initial cut of ob3 verifier
This commit is contained in:
		
							parent
							
								
									e664dffe2d
								
							
						
					
					
						commit
						068a6edb8d
					
				
							
								
								
									
										15
									
								
								inspector-vc/pom.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								inspector-vc/pom.xml
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,15 @@
 | 
			
		||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
 | 
			
		||||
  <modelVersion>4.0.0</modelVersion>
 | 
			
		||||
  <parent>
 | 
			
		||||
    <groupId>org.1edtech</groupId>
 | 
			
		||||
    <artifactId>inspector</artifactId>
 | 
			
		||||
    <version>0.9.2</version>
 | 
			
		||||
  </parent>
 | 
			
		||||
  <artifactId>inspector-vc</artifactId>
 | 
			
		||||
  <dependencies>
 | 
			
		||||
		<dependency>
 | 
			
		||||
			<groupId>org.1edtech</groupId>
 | 
			
		||||
			<artifactId>inspector-core</artifactId>			
 | 
			
		||||
		</dependency>		
 | 
			
		||||
	</dependencies>
 | 
			
		||||
</project>
 | 
			
		||||
@ -0,0 +1,107 @@
 | 
			
		||||
package org.oneedtech.inspect.vc;
 | 
			
		||||
 | 
			
		||||
import static org.oneedtech.inspect.util.code.Defensives.*;
 | 
			
		||||
import static org.oneedtech.inspect.util.resource.ResourceType.*;
 | 
			
		||||
 | 
			
		||||
import java.util.Iterator;
 | 
			
		||||
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;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 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;
 | 
			
		||||
	
 | 
			
		||||
	public Credential(Resource resource, JsonNode data) {
 | 
			
		||||
		super(ID, GeneratedObject.Type.INTERNAL);
 | 
			
		||||
		checkNotNull(resource, resource.getType(), data);
 | 
			
		||||
		ResourceType type = resource.getType();
 | 
			
		||||
		checkTrue(type == SVG || type == PNG || type == JSON || type == JWT, 
 | 
			
		||||
				"Unrecognized payload type: " + type.getName());
 | 
			
		||||
		this.resource = resource;
 | 
			
		||||
		this.jsonData = data;
 | 
			
		||||
		
 | 
			
		||||
		ArrayNode typeNode = (ArrayNode)jsonData.get("type");		
 | 
			
		||||
		this.credentialType = Credential.Type.valueOf(typeNode);
 | 
			
		||||
		
 | 
			
		||||
	}
 | 
			
		||||
		
 | 
			
		||||
	public Resource getResource() {
 | 
			
		||||
		return resource;
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	public JsonNode asJson() {
 | 
			
		||||
		return jsonData;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public Credential.Type getCredentialType() {
 | 
			
		||||
		return credentialType;
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	/**
 | 
			
		||||
	 * Get the canonical schema for this credential if such exists.
 | 
			
		||||
	 */
 | 
			
		||||
	public Optional<SchemaKey> getSchemaKey() {
 | 
			
		||||
		if(credentialType == Credential.Type.AchievementCredential) {
 | 
			
		||||
			return Optional.of(Catalog.OB_30_ACHIEVEMENTCREDENTIAL_JSON);
 | 
			
		||||
		} else if(credentialType == Credential.Type.VerifiablePresentation) {
 | 
			
		||||
			return Optional.of(Catalog.OB_30_VERIFIABLEPRESENTATION_JSON);
 | 
			
		||||
		} else if(credentialType == Credential.Type.EndorsementCredential) {
 | 
			
		||||
			return Optional.of(Catalog.OB_30_ENDORSEMENTCREDENTIAL_JSON);
 | 
			
		||||
		} 		
 | 
			
		||||
		return Optional.empty();
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	public enum Type {
 | 
			
		||||
		AchievementCredential,
 | 
			
		||||
		OpenBadgeCredential, 	//treated as an alias of AchievementCredential
 | 
			
		||||
		EndorsementCredential,
 | 
			
		||||
		VerifiablePresentation,
 | 
			
		||||
		VerifiableCredential,  //this is an underspecifier in our context
 | 
			
		||||
		Unknown;	
 | 
			
		||||
		
 | 
			
		||||
		public static Credential.Type valueOf (ArrayNode typeArray) {
 | 
			
		||||
			if(typeArray != null) {
 | 
			
		||||
				Iterator<JsonNode> iter = typeArray.iterator();
 | 
			
		||||
				while(iter.hasNext()) {
 | 
			
		||||
					String value = iter.next().asText();
 | 
			
		||||
					if(value.equals("AchievementCredential") || value.equals("OpenBadgeCredential")) {
 | 
			
		||||
						return AchievementCredential;
 | 
			
		||||
					} else if(value.equals("VerifiablePresentation")) {
 | 
			
		||||
						return VerifiablePresentation;
 | 
			
		||||
					} else if(value.equals("EndorsementCredential")) {
 | 
			
		||||
						return EndorsementCredential;
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			return Unknown;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	@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();
 | 
			
		||||
		
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,69 @@
 | 
			
		||||
package org.oneedtech.inspect.vc;
 | 
			
		||||
 | 
			
		||||
import static org.oneedtech.inspect.core.probe.RunContext.Key.*;
 | 
			
		||||
import static org.oneedtech.inspect.util.json.ObjectMapperCache.Config.DEFAULT;
 | 
			
		||||
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
 | 
			
		||||
import org.oneedtech.inspect.core.SubInspector;
 | 
			
		||||
import org.oneedtech.inspect.core.probe.GeneratedObject;
 | 
			
		||||
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.report.Report;
 | 
			
		||||
import org.oneedtech.inspect.core.report.ReportItems;
 | 
			
		||||
import org.oneedtech.inspect.util.json.ObjectMapperCache;
 | 
			
		||||
import org.oneedtech.inspect.util.resource.Resource;
 | 
			
		||||
 | 
			
		||||
import com.fasterxml.jackson.databind.ObjectMapper;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * An inspector for EndersementCredential objects. 
 | 
			
		||||
 * @author mgylling
 | 
			
		||||
 */
 | 
			
		||||
public class EndorsementInspector extends VCInspector implements SubInspector {
 | 
			
		||||
 | 
			
		||||
	protected <B extends VCInspector.Builder<?>> EndorsementInspector(B builder) {
 | 
			
		||||
		super(builder);
 | 
			
		||||
	}	
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public Report run(Resource resource, Map<String, GeneratedObject> parentObjects) {
 | 
			
		||||
		/*
 | 
			
		||||
		 * resource is the top-level credential that embeds the endorsement, we
 | 
			
		||||
		 * expect parentObjects to provide a pointer to the JsonNode we should check
 | 
			
		||||
		 */
 | 
			
		||||
		Credential endorsement = (Credential) parentObjects.get(ENDORSEMENT_KEY);
 | 
			
		||||
		
 | 
			
		||||
		ObjectMapper mapper = ObjectMapperCache.get(DEFAULT);
 | 
			
		||||
		JsonPathEvaluator jsonPath = new JsonPathEvaluator(mapper);
 | 
			
		||||
		
 | 
			
		||||
		RunContext ctx = new RunContext.Builder()
 | 
			
		||||
				.put(this)
 | 
			
		||||
				.put(resource)
 | 
			
		||||
				.put(JACKSON_OBJECTMAPPER, mapper)
 | 
			
		||||
				.put(JSONPATH_EVALUATOR, jsonPath)
 | 
			
		||||
				.put(ENDORSEMENT_KEY, endorsement)
 | 
			
		||||
				.build();
 | 
			
		||||
		
 | 
			
		||||
		System.err.println("TODO" + endorsement.toString());
 | 
			
		||||
		
 | 
			
		||||
		return new Report(ctx, new ReportItems(), 1); //TODO
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public <R extends Resource> Report run(R resource) {
 | 
			
		||||
		throw new IllegalStateException("must use #run(resource, map)");
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	public static class Builder extends VCInspector.Builder<EndorsementInspector.Builder> {
 | 
			
		||||
		@SuppressWarnings("unchecked")
 | 
			
		||||
		@Override
 | 
			
		||||
		public EndorsementInspector build() {
 | 
			
		||||
			return new EndorsementInspector(this);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	public static final String ENDORSEMENT_KEY = "ENDORSEMENT_KEY";
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,195 @@
 | 
			
		||||
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.json.ObjectMapperCache.Config.DEFAULT;
 | 
			
		||||
import static org.oneedtech.inspect.vc.EndorsementInspector.ENDORSEMENT_KEY;
 | 
			
		||||
import static org.oneedtech.inspect.vc.util.JsonNodeUtil.getEndorsements;
 | 
			
		||||
 | 
			
		||||
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.probe.Outcome;
 | 
			
		||||
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.JsonArrayProbe;
 | 
			
		||||
import org.oneedtech.inspect.core.probe.json.JsonPathEvaluator;
 | 
			
		||||
import org.oneedtech.inspect.core.probe.json.JsonPredicates.JsonPredicateProbeParams;
 | 
			
		||||
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.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.probe.CredentialTypeProbe;
 | 
			
		||||
import org.oneedtech.inspect.vc.probe.ExpirationVerifierProbe;
 | 
			
		||||
import org.oneedtech.inspect.vc.probe.InlineJsonSchemaProbe;
 | 
			
		||||
import org.oneedtech.inspect.vc.probe.IssuanceVerifierProbe;
 | 
			
		||||
import org.oneedtech.inspect.vc.probe.Predicates;
 | 
			
		||||
import org.oneedtech.inspect.vc.probe.ProofVerifierProbe;
 | 
			
		||||
import org.oneedtech.inspect.vc.probe.RevocationListProbe;
 | 
			
		||||
import org.oneedtech.inspect.vc.probe.SignatureVerifierProbe;
 | 
			
		||||
 | 
			
		||||
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 {
 | 
			
		||||
	protected final List<Probe<Credential>> 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
 | 
			
		||||
	
 | 
			
		||||
	@Override
 | 
			
		||||
	public Report run(Resource resource) {		
 | 
			
		||||
		super.check(resource);		
 | 
			
		||||
		
 | 
			
		||||
		if(getBehavior(RESET_CACHES_ON_RUN) == TRUE) JsonSchemaCache.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<ReportItems> accumulator = new ArrayList<>();
 | 
			
		||||
		int probeCount = 0;
 | 
			
		||||
		
 | 
			
		||||
		try {							
 | 
			
		||||
				//detect type (png, svg, json, jwt) and extract json data
 | 
			
		||||
				probeCount++;
 | 
			
		||||
				accumulator.add(new CredentialTypeProbe().run(resource, ctx));				
 | 
			
		||||
				if(broken(accumulator)) return abort(ctx, accumulator, probeCount);
 | 
			
		||||
				
 | 
			
		||||
				//we expect the above to place a generated object in the context				
 | 
			
		||||
				Credential crd = ctx.getGeneratedObject(Credential.ID);
 | 
			
		||||
				
 | 
			
		||||
				//validate the value of the type property
 | 
			
		||||
				probeCount++;
 | 
			
		||||
				accumulator.add(new JsonArrayProbe(vcType).run(crd.asJson(), ctx));
 | 
			
		||||
				probeCount++;	
 | 
			
		||||
				accumulator.add(new JsonArrayProbe(obType).run(crd.asJson(), ctx));
 | 
			
		||||
				if(broken(accumulator)) return abort(ctx, accumulator, probeCount);		
 | 
			
		||||
				
 | 
			
		||||
				//validate against the canonical schema	 	
 | 
			
		||||
				SchemaKey canonical = crd.getSchemaKey().orElseThrow();
 | 
			
		||||
				probeCount++;
 | 
			
		||||
				accumulator.add(new JsonSchemaProbe(canonical).run(crd.asJson(), ctx));
 | 
			
		||||
				
 | 
			
		||||
				//validate against any inline schemas 	
 | 
			
		||||
				probeCount++;
 | 
			
		||||
				accumulator.add(new InlineJsonSchemaProbe().run(crd, ctx));
 | 
			
		||||
				
 | 
			
		||||
				//verify signatures TODO @Miles
 | 
			
		||||
				probeCount++;
 | 
			
		||||
				accumulator.add(new SignatureVerifierProbe().run(crd, ctx));
 | 
			
		||||
				if(broken(accumulator)) return abort(ctx, accumulator, probeCount);
 | 
			
		||||
				
 | 
			
		||||
				//verify proofs TODO @Miles
 | 
			
		||||
				probeCount++;
 | 
			
		||||
				accumulator.add(new ProofVerifierProbe().run(crd, 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<String> newID = checkRefreshService(crd, ctx); //TODO fail = invalid
 | 
			
		||||
					if(newID.isPresent()) {						
 | 
			
		||||
						return this.run(
 | 
			
		||||
							new UriResource(new URI(newID.get()))
 | 
			
		||||
								.setContext(new ResourceContext(REFRESHED, TRUE)));
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
				
 | 
			
		||||
				//check revocation status
 | 
			
		||||
				probeCount++;
 | 
			
		||||
				accumulator.add(new RevocationListProbe().run(crd, ctx));
 | 
			
		||||
				if(broken(accumulator)) return abort(ctx, accumulator, probeCount);
 | 
			
		||||
				
 | 
			
		||||
				//check expiration
 | 
			
		||||
				probeCount++;
 | 
			
		||||
				accumulator.add(new ExpirationVerifierProbe().run(crd, ctx));
 | 
			
		||||
				if(broken(accumulator)) return abort(ctx, accumulator, probeCount);
 | 
			
		||||
				
 | 
			
		||||
				//check issuance
 | 
			
		||||
				probeCount++;
 | 
			
		||||
				accumulator.add(new IssuanceVerifierProbe().run(crd, ctx));
 | 
			
		||||
				if(broken(accumulator)) return abort(ctx, accumulator, probeCount);
 | 
			
		||||
				
 | 
			
		||||
				//embedded endorsements 
 | 
			
		||||
				List<JsonNode> endorsements = getEndorsements(crd.asJson(), jsonPath);
 | 
			
		||||
				if(endorsements.size() > 0) {
 | 
			
		||||
					EndorsementInspector subInspector = new EndorsementInspector.Builder().build();	
 | 
			
		||||
					for(JsonNode endorsementNode : endorsements) {
 | 
			
		||||
						probeCount++;
 | 
			
		||||
						Credential endorsement = new Credential(resource, endorsementNode);
 | 
			
		||||
						accumulator.add(subInspector.run(resource, Map.of(ENDORSEMENT_KEY, endorsement)));
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
				
 | 
			
		||||
				//finally, run any user-added probes
 | 
			
		||||
				for(Probe<Credential> probe : userProbes) {
 | 
			
		||||
					probeCount++;
 | 
			
		||||
					accumulator.add(probe.run(crd, ctx));
 | 
			
		||||
				}
 | 
			
		||||
						
 | 
			
		||||
		} catch (Exception e) {
 | 
			
		||||
			accumulator.add(onProbeException(Probe.ID.NO_UNCAUGHT_EXCEPTIONS, resource, e));
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		return new Report(ctx, new ReportItems(accumulator), probeCount); 
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	/**
 | 
			
		||||
	 * 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.
 | 
			
		||||
	 */
 | 
			
		||||
	private Optional<String> checkRefreshService(Credential crd, RunContext ctx) {
 | 
			
		||||
		//TODO
 | 
			
		||||
		return Optional.empty();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private static final String REFRESHED = "is.refreshed.credential";
 | 
			
		||||
	
 | 
			
		||||
	private static final JsonPredicateProbeParams obType = JsonPredicateProbeParams.of(
 | 
			
		||||
			"$.type", Predicates.OB30.TypeProperty.value, Predicates.OB30.TypeProperty.msg, Outcome.FATAL);
 | 
			
		||||
	
 | 
			
		||||
	private static final JsonPredicateProbeParams vcType = JsonPredicateProbeParams.of(
 | 
			
		||||
			"$.type", Predicates.VC.TypeProperty.value, Predicates.VC.TypeProperty.msg, Outcome.FATAL);
 | 
			
		||||
	
 | 
			
		||||
	public static class Builder extends VCInspector.Builder<OB30Inspector.Builder> {
 | 
			
		||||
		@SuppressWarnings("unchecked")
 | 
			
		||||
		@Override
 | 
			
		||||
		public OB30Inspector build() {
 | 
			
		||||
			set(Specification.OB30);
 | 
			
		||||
			set(ResourceType.OPENBADGE);
 | 
			
		||||
			return new OB30Inspector(this);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,47 @@
 | 
			
		||||
package org.oneedtech.inspect.vc;
 | 
			
		||||
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
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;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Abstract base for verifiable credentials inspectors/verifiers.
 | 
			
		||||
 * @author mgylling
 | 
			
		||||
 */
 | 
			
		||||
public abstract class VCInspector extends Inspector {
 | 
			
		||||
	
 | 
			
		||||
	protected <B extends VCInspector.Builder<?>> VCInspector(B builder) {
 | 
			
		||||
		super(builder);		
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	protected Report abort(RunContext ctx, List<ReportItems> accumulator, int probeCount) {
 | 
			
		||||
		return new Report(ctx, new ReportItems(accumulator), probeCount);
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	protected boolean broken(List<ReportItems> accumulator) {
 | 
			
		||||
		for(ReportItems items : accumulator) {
 | 
			
		||||
			if(items.contains(Outcome.FATAL, Outcome.EXCEPTION, Outcome.NOT_RUN)) return true;
 | 
			
		||||
		}
 | 
			
		||||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	public abstract static class Builder<B extends VCInspector.Builder<B>> extends Inspector.Builder<B> {
 | 
			
		||||
		final List<Probe<Credential>> probes; 
 | 
			
		||||
 | 
			
		||||
		public Builder() {
 | 
			
		||||
			super();
 | 
			
		||||
			this.probes = new ArrayList<>();
 | 
			
		||||
		}
 | 
			
		||||
				
 | 
			
		||||
		public VCInspector.Builder<B> add(Probe<Credential> probe) {
 | 
			
		||||
			probes.add(probe);
 | 
			
		||||
			return this;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,165 @@
 | 
			
		||||
package org.oneedtech.inspect.vc.probe;
 | 
			
		||||
 | 
			
		||||
import static java.nio.charset.StandardCharsets.UTF_8;
 | 
			
		||||
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
import java.io.InputStream;
 | 
			
		||||
import java.util.Base64;
 | 
			
		||||
import java.util.Base64.Decoder;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.Optional;
 | 
			
		||||
 | 
			
		||||
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.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.util.xml.XMLInputFactoryCache;
 | 
			
		||||
import org.oneedtech.inspect.vc.Credential;
 | 
			
		||||
 | 
			
		||||
import com.fasterxml.jackson.databind.JsonNode;
 | 
			
		||||
import com.fasterxml.jackson.databind.ObjectMapper;
 | 
			
		||||
import com.google.common.base.Splitter;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A probe that verifies that the incoming credential resource is of a recognized type, 
 | 
			
		||||
 * and if so extracts and stores the VC json data (a 'Credential' instance) in the RunContext.   
 | 
			
		||||
 * @author mgylling
 | 
			
		||||
 */
 | 
			
		||||
public class CredentialTypeProbe extends Probe<Resource> {
 | 
			
		||||
			
 | 
			
		||||
	@Override
 | 
			
		||||
	public ReportItems run(Resource resource, RunContext context) throws Exception {
 | 
			
		||||
		
 | 
			
		||||
		Credential crd = null; 
 | 
			
		||||
		try {
 | 
			
		||||
			Optional<ResourceType> type = TypeDetector.detect(resource, true);
 | 
			
		||||
			
 | 
			
		||||
			if(type.isPresent()) {
 | 
			
		||||
				resource.setType(type.get());
 | 
			
		||||
				if(type.get() == ResourceType.PNG) {
 | 
			
		||||
					crd = new Credential(resource, fromPNG(resource, context));
 | 
			
		||||
				} else if(type.get() == ResourceType.SVG) {
 | 
			
		||||
					crd = new Credential(resource, fromSVG(resource, context));
 | 
			
		||||
				} else if(type.get() == ResourceType.JSON) {
 | 
			
		||||
					crd = new Credential(resource, fromJson(resource, context));
 | 
			
		||||
				} else if(type.get() == ResourceType.JWT) {
 | 
			
		||||
					crd = new Credential(resource, fromJWT(resource, context));
 | 
			
		||||
				}
 | 
			
		||||
			} 
 | 
			
		||||
					
 | 
			
		||||
			if(crd != null) {
 | 
			
		||||
				context.addGeneratedObject(crd);
 | 
			
		||||
				return success(this, context);		
 | 
			
		||||
			} else {
 | 
			
		||||
				return fatal("Could not detect credential type", context);	
 | 
			
		||||
			}
 | 
			
		||||
			
 | 
			
		||||
		} catch (Exception e) {
 | 
			
		||||
			return fatal("Error while detecting credential type: " + e.getMessage(), context);
 | 
			
		||||
		}		
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Extract the JSON data from a baked PNG credential.
 | 
			
		||||
	 * @param context 
 | 
			
		||||
	 * @throws Exception 
 | 
			
		||||
	 */
 | 
			
		||||
	private JsonNode fromPNG(Resource resource, RunContext context) throws Exception {
 | 
			
		||||
		//TODO @Miles - note: iTxt chunk is either plain json or jwt	
 | 
			
		||||
		try(InputStream is = resource.asByteSource().openStream()) {
 | 
			
		||||
			
 | 
			
		||||
		}
 | 
			
		||||
		return null;
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	/**
 | 
			
		||||
	 * Extract the JSON data from a baked SVG credential.
 | 
			
		||||
	 * @param context 
 | 
			
		||||
	 * @throws Exception 
 | 
			
		||||
	 */
 | 
			
		||||
	private JsonNode fromSVG(Resource resource, RunContext context) throws Exception {
 | 
			
		||||
		String json = null;
 | 
			
		||||
		try(InputStream is = resource.asByteSource().openStream()) {
 | 
			
		||||
			XMLEventReader reader = XMLInputFactoryCache.getInstance().createXMLEventReader(is);
 | 
			
		||||
			while(reader.hasNext()) {
 | 
			
		||||
				XMLEvent ev = reader.nextEvent();
 | 
			
		||||
				if(ev.isStartElement() && ev.asStartElement().getName().equals(OB_CRED_ELEM)) {
 | 
			
		||||
					Attribute verifyAttr = ev.asStartElement().getAttributeByName(OB_CRED_VERIFY_ATTR);
 | 
			
		||||
					if(verifyAttr != null) {
 | 
			
		||||
						json = decodeJWT(verifyAttr.getValue());
 | 
			
		||||
						break;
 | 
			
		||||
					} else {
 | 
			
		||||
						while(reader.hasNext()) {
 | 
			
		||||
							ev = reader.nextEvent();
 | 
			
		||||
							if(ev.isEndElement() && ev.asEndElement().getName().equals(OB_CRED_ELEM)) {
 | 
			
		||||
								break;
 | 
			
		||||
							}
 | 
			
		||||
							if(ev.getEventType() == XMLEvent.CHARACTERS) {
 | 
			
		||||
								Characters chars = ev.asCharacters();
 | 
			
		||||
								if(!chars.isWhiteSpace()) {
 | 
			
		||||
									json = chars.getData();
 | 
			
		||||
									break;
 | 
			
		||||
								}
 | 
			
		||||
							}
 | 
			
		||||
						}						
 | 
			
		||||
					}					
 | 
			
		||||
				}	
 | 
			
		||||
				if(json!=null) break;
 | 
			
		||||
			}
 | 
			
		||||
		}	
 | 
			
		||||
		if(json == null) throw new IllegalArgumentException("No credential inside SVG");		
 | 
			
		||||
		return fromString(json, context);
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	/**
 | 
			
		||||
	 * Create a JsonNode object from a raw JSON resource.
 | 
			
		||||
	 * @param context 
 | 
			
		||||
	 */
 | 
			
		||||
	private JsonNode fromJson(Resource resource, RunContext context) throws Exception {
 | 
			
		||||
		return fromString(resource.asByteSource().asCharSource(UTF_8).read(), context);
 | 
			
		||||
	}
 | 
			
		||||
		
 | 
			
		||||
	/**
 | 
			
		||||
	 * Create a JsonNode object from a String.
 | 
			
		||||
	 */
 | 
			
		||||
	private JsonNode fromString(String json, RunContext context) throws Exception {
 | 
			
		||||
		return ((ObjectMapper)context.get(RunContext.Key.JACKSON_OBJECTMAPPER)).readTree(json);
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	/**
 | 
			
		||||
	 * Create a JsonNode object from a JWT resource.
 | 
			
		||||
	 * @param context 
 | 
			
		||||
	 */
 | 
			
		||||
	private JsonNode fromJWT(Resource resource, RunContext context) throws Exception {
 | 
			
		||||
		return fromString(decodeJWT(resource.asByteSource().asCharSource(UTF_8).read()), context);
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	/**
 | 
			
		||||
	 * Decode as per https://www.imsglobal.org/spec/ob/v3p0/#jwt-proof
 | 
			
		||||
	 * @return The decoded JSON String
 | 
			
		||||
	 */
 | 
			
		||||
	private String decodeJWT(String jwt) {
 | 
			
		||||
		List<String> 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)));
 | 
			
		||||
		String jwtPayload = new String(decoder.decode(parts.get(1)));
 | 
			
		||||
		String jwsSignature = new String(decoder.decode(parts.get(2)));
 | 
			
		||||
				
 | 
			
		||||
		//TODO @Miles
 | 
			
		||||
		
 | 
			
		||||
		return null;	
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	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");
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,47 @@
 | 
			
		||||
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 ExpirationVerifierProbe extends Probe<Credential> {
 | 
			
		||||
	
 | 
			
		||||
	public ExpirationVerifierProbe() {
 | 
			
		||||
		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. 
 | 
			
		||||
		 */
 | 
			
		||||
 | 
			
		||||
		ZonedDateTime now = ZonedDateTime.now();			
 | 
			
		||||
		JsonNode node = crd.asJson().get("expirationDate");
 | 
			
		||||
		if(node != null) {
 | 
			
		||||
			ZonedDateTime expirationDate = null;
 | 
			
		||||
			try {
 | 
			
		||||
				expirationDate = ZonedDateTime.parse(node.textValue());
 | 
			
		||||
				if (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 = ExpirationVerifierProbe.class.getSimpleName(); 
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,71 @@
 | 
			
		||||
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<Credential> {
 | 
			
		||||
	private static final Set<String> types = Set.of("1EdTechJsonSchemaValidator2019");
 | 
			
		||||
	private final boolean skipCanonical = true;
 | 
			
		||||
		
 | 
			
		||||
	public InlineJsonSchemaProbe() {
 | 
			
		||||
		super(ID);
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	@Override
 | 
			
		||||
	public ReportItems run(Credential crd, RunContext ctx) throws Exception {
 | 
			
		||||
		List<ReportItems> accumulator = new ArrayList<>();
 | 
			
		||||
		Set<String> ioErrors = new HashSet<>();		
 | 
			
		||||
 | 
			
		||||
//		JsonPathEvaluator jsonPath = ctx.get(RunContext.Key.JSONPATH_EVALUATOR);		
 | 
			
		||||
//		ArrayNode nodes = jsonPath.eval("$..*[?(@.credentialSchema)]", crd.getJson());
 | 
			
		||||
// 		note - we dont get deep nested ones in e.g. EndorsementCredential 
 | 
			
		||||
		
 | 
			
		||||
		JsonNode credentialSchemaNode = crd.asJson().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(skipCanonical && equals(crd.getSchemaKey(), id)) continue;				
 | 
			
		||||
			try {								
 | 
			
		||||
				accumulator.add(new JsonSchemaProbe(id).run(crd.asJson(), 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(Optional<SchemaKey> key, String id) {
 | 
			
		||||
		return key.isPresent() && key.get().getCanonicalURI().equals(id);					
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	public static final String ID = InlineJsonSchemaProbe.class.getSimpleName();
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,47 @@
 | 
			
		||||
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 IssuanceVerifierProbe extends Probe<Credential> {
 | 
			
		||||
	
 | 
			
		||||
	public IssuanceVerifierProbe() {
 | 
			
		||||
		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.
 | 
			
		||||
		 */
 | 
			
		||||
				
 | 
			
		||||
		ZonedDateTime now = ZonedDateTime.now();				
 | 
			
		||||
		JsonNode node = crd.asJson().get("issuanceDate");
 | 
			
		||||
		if(node != null) {
 | 
			
		||||
			ZonedDateTime issuanceDate = null;
 | 
			
		||||
			try {
 | 
			
		||||
				issuanceDate = ZonedDateTime.parse(node.textValue());
 | 
			
		||||
				if (issuanceDate.isAfter(now)) {
 | 
			
		||||
					return fatal("The credential is not yet valid (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 = IssuanceVerifierProbe.class.getSimpleName(); 
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,51 @@
 | 
			
		||||
package org.oneedtech.inspect.vc.probe;
 | 
			
		||||
 | 
			
		||||
import static org.oneedtech.inspect.vc.Credential.Type.*;
 | 
			
		||||
import static org.oneedtech.inspect.vc.util.JsonNodeUtil.asStringList;
 | 
			
		||||
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.function.Predicate;
 | 
			
		||||
 | 
			
		||||
import com.fasterxml.jackson.databind.JsonNode;
 | 
			
		||||
import com.google.common.base.Joiner;
 | 
			
		||||
 | 
			
		||||
//TODO refactor
 | 
			
		||||
public class Predicates {
 | 
			
		||||
	
 | 
			
		||||
	public static class OB30 {	
 | 
			
		||||
		public static class TypeProperty {			
 | 
			
		||||
			public static final Predicate<JsonNode> value = new Predicate<>() {						
 | 
			
		||||
				@Override
 | 
			
		||||
				public boolean test(JsonNode node) {
 | 
			
		||||
					List<String> values = asStringList(node);
 | 
			
		||||
					for(String exp : exp) {
 | 
			
		||||
						if(values.contains(exp)) return true;
 | 
			
		||||
					}			
 | 
			
		||||
					return false;
 | 
			
		||||
				}				
 | 
			
		||||
			};
 | 
			
		||||
			private static final List<String> exp = List.of(OpenBadgeCredential.name(), AchievementCredential.name(), VerifiablePresentation.name());
 | 
			
		||||
			public static final String msg = "The type property does not contain one of " + Joiner.on(", ").join(exp);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	public static class VC {	
 | 
			
		||||
		public static class TypeProperty {			
 | 
			
		||||
			public static final Predicate<JsonNode> value = new Predicate<>() {						
 | 
			
		||||
				@Override
 | 
			
		||||
				public boolean test(JsonNode node) {
 | 
			
		||||
					List<String> values = asStringList(node);			
 | 
			
		||||
					if(values.contains(exp)) return true;					
 | 
			
		||||
					return false;
 | 
			
		||||
				}				
 | 
			
		||||
			};
 | 
			
		||||
			private static final String exp = VerifiableCredential.name();
 | 
			
		||||
			public static final String msg = "The type property does not contain " + exp;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	
 | 
			
		||||
		
 | 
			
		||||
	
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,27 @@
 | 
			
		||||
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 org.oneedtech.inspect.vc.Credential;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A Probe that verifies credential proofs 
 | 
			
		||||
 * @author mlyon
 | 
			
		||||
 */
 | 
			
		||||
public class ProofVerifierProbe extends Probe<Credential> {
 | 
			
		||||
	
 | 
			
		||||
	public ProofVerifierProbe() {
 | 
			
		||||
		super(ID);
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	@Override
 | 
			
		||||
	public ReportItems run(Credential crd, RunContext ctx) throws Exception {
 | 
			
		||||
 | 
			
		||||
		//TODO @Miles -- if proofs fail, report OutCome.Fatal
 | 
			
		||||
								
 | 
			
		||||
		return success(ctx);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public static final String ID = ProofVerifierProbe.class.getSimpleName();
 | 
			
		||||
}
 | 
			
		||||
@ -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<Credential> {
 | 
			
		||||
	
 | 
			
		||||
	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.asJson().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.asJson().get("id");
 | 
			
		||||
					        if(crdID != null) {
 | 
			
		||||
					        	List<JsonNode> 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(); 
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,28 @@
 | 
			
		||||
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 org.oneedtech.inspect.vc.Credential;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A Probe that verifies credential signatures 
 | 
			
		||||
 * @author mlyon
 | 
			
		||||
 */
 | 
			
		||||
public class SignatureVerifierProbe extends Probe<Credential> {
 | 
			
		||||
	
 | 
			
		||||
	public SignatureVerifierProbe() {
 | 
			
		||||
		super(ID);
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	@Override
 | 
			
		||||
	public ReportItems run(Credential crd, RunContext ctx) throws Exception {
 | 
			
		||||
 | 
			
		||||
		//TODO @Miles -- if sigs fail, report OutCome.Fatal
 | 
			
		||||
					
 | 
			
		||||
		return success(ctx);
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	public static final String ID = SignatureVerifierProbe.class.getSimpleName();
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -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;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Node access utilities.
 | 
			
		||||
 * @author mgylling
 | 
			
		||||
 */
 | 
			
		||||
public class JsonNodeUtil {
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Get all embedded endorsement objects as a flat list.
 | 
			
		||||
	 * @return a List that is never null but may be empty.
 | 
			
		||||
	 */
 | 
			
		||||
	public static List<JsonNode> getEndorsements(JsonNode root, JsonPathEvaluator jsonPath) {
 | 
			
		||||
		List<JsonNode> list = new ArrayList<>();		
 | 
			
		||||
		ArrayNode endorsements = jsonPath.eval("$..endorsement", root);	
 | 
			
		||||
		for(JsonNode endorsement : endorsements) {
 | 
			
		||||
			ArrayNode values = (ArrayNode) endorsement;
 | 
			
		||||
			for(JsonNode value : values) {
 | 
			
		||||
				list.add(value);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		return list;
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	public static List<String> 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<JsonNode> 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());	
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,94 @@
 | 
			
		||||
package org.oneedtech.inspect.vc;
 | 
			
		||||
 | 
			
		||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
 | 
			
		||||
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.report.Report;
 | 
			
		||||
import org.oneedtech.inspect.test.PrintHelper;
 | 
			
		||||
import org.oneedtech.inspect.vc.probe.InlineJsonSchemaProbe;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
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)				
 | 
			
		||||
				.build();		
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	@Test
 | 
			
		||||
	void testSimpleJsonValid() {
 | 
			
		||||
		assertDoesNotThrow(()->{
 | 
			
		||||
			Report report = validator.run(Samples.OB30.JSON.SIMPLE_JSON.asFileResource());
 | 
			
		||||
			if(verbose) PrintHelper.print(report, true);
 | 
			
		||||
			assertValid(report);			
 | 
			
		||||
		});	
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	@Disabled
 | 
			
		||||
	@Test
 | 
			
		||||
	void testSimplePNGPlainValid() {
 | 
			
		||||
		assertDoesNotThrow(()->{
 | 
			
		||||
			Report report = validator.run(Samples.OB30.PNG.SIMPLE_JSON_PNG.asFileResource());
 | 
			
		||||
			if(verbose) PrintHelper.print(report, true);
 | 
			
		||||
			assertValid(report);			
 | 
			
		||||
		});	
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	@Disabled
 | 
			
		||||
	@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);			
 | 
			
		||||
		});	
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	@Disabled
 | 
			
		||||
	@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() {
 | 
			
		||||
		assertDoesNotThrow(()->{
 | 
			
		||||
			Report report = validator.run(Samples.OB30.JSON.SIMPLE_JSON_UNKNOWN_TYPE.asFileResource());
 | 
			
		||||
			if(verbose) PrintHelper.print(report, true);
 | 
			
		||||
			assertInvalid(report);
 | 
			
		||||
		});	
 | 
			
		||||
	}
 | 
			
		||||
		
 | 
			
		||||
	@Test
 | 
			
		||||
	void testCompleteJsonInvalidInlineSchemaRef() throws Exception {
 | 
			
		||||
		assertDoesNotThrow(()->{
 | 
			
		||||
			Report report = validator.run(Samples.OB30.JSON.COMPLETE_JSON.asFileResource());
 | 
			
		||||
			if(verbose) PrintHelper.print(report, true);
 | 
			
		||||
			assertInvalid(report);
 | 
			
		||||
			assertErrorCount(report, 1);
 | 
			
		||||
			assertHasProbeID(report, InlineJsonSchemaProbe.ID, true);									
 | 
			
		||||
		});	
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,25 @@
 | 
			
		||||
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_UNKNOWN_TYPE = new Sample("ob30/simple-unknown-type.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);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,30 @@
 | 
			
		||||
package org.oneedtech.inspect.vc.util;
 | 
			
		||||
 | 
			
		||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
 | 
			
		||||
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;
 | 
			
		||||
 | 
			
		||||
public class JsonNodeUtilTests {
 | 
			
		||||
	static final ObjectMapper mapper = ObjectMapperCache.get(DEFAULT);
 | 
			
		||||
	static final JsonPathEvaluator jsonPath = new JsonPathEvaluator(mapper);
 | 
			
		||||
	
 | 
			
		||||
	
 | 
			
		||||
	@Test
 | 
			
		||||
	void getEndorsementsTest() throws Exception {
 | 
			
		||||
		assertDoesNotThrow(()->{
 | 
			
		||||
			JsonNode root =  mapper.readTree(Samples.OB30.JSON.COMPLETE_JSON.asBytes());			
 | 
			
		||||
			List<JsonNode> list = JsonNodeUtil.getEndorsements(root, jsonPath);
 | 
			
		||||
			Assertions.assertEquals(5, list.size());
 | 
			
		||||
		});	
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										712
									
								
								inspector-vc/src/test/resources/ob30/complete.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										712
									
								
								inspector-vc/src/test/resources/ob30/complete.json
									
									
									
									
									
										Normal file
									
								
							@ -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"
 | 
			
		||||
        }
 | 
			
		||||
    ]
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								inspector-vc/src/test/resources/ob30/simple-json.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								inspector-vc/src/test/resources/ob30/simple-json.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 82 KiB  | 
							
								
								
									
										56
									
								
								inspector-vc/src/test/resources/ob30/simple-json.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								inspector-vc/src/test/resources/ob30/simple-json.svg
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,56 @@
 | 
			
		||||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
 | 
			
		||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:openbadges="https://purl.imsglobal.org/ob/v3p0" x="0px" y="0px" viewBox="0 0 1000 1000" enable-background="new 0 0 1000 1000" xml:space="preserve">
 | 
			
		||||
  <openbadges:credential>
 | 
			
		||||
    <![CDATA[
 | 
			
		||||
      {
 | 
			
		||||
        "@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"
 | 
			
		||||
          ]
 | 
			
		||||
        },
 | 
			
		||||
        "proof": [
 | 
			
		||||
          {
 | 
			
		||||
            "type": "Ed25519Signature2020",
 | 
			
		||||
            "created": "2022-06-09T22:56:28Z",
 | 
			
		||||
            "verificationMethod": "https://example.edu/issuers/565049#key-1",
 | 
			
		||||
            "proofPurpose": "assertionMethod",
 | 
			
		||||
            "proofValue": "z58ieJCh4kN6eE2Vq4TyYURKSC4hWWEK7b75NNUL2taZMhKqwTteuByG1wRoGDdCqqNLW5Gq1diUi4qyZ63tQRtyN"
 | 
			
		||||
          }
 | 
			
		||||
        ]
 | 
			
		||||
      }
 | 
			
		||||
    ]]>
 | 
			
		||||
  </openbadges:credential>
 | 
			
		||||
  <g>
 | 
			
		||||
    <path fill="none" stroke="#040000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" d="M500,928.8c0,0,0,40.8,245,40.8" />
 | 
			
		||||
    <path fill="none" stroke="#040000" stroke-width="1.9215" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" d="M500,928.8c0,0,40.8,0,285.8,0" />
 | 
			
		||||
    <path fill="none" stroke="#040000" stroke-width="1.9215" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" d="M214.2,928.8c0,0,40.8,0,285.8,0" />
 | 
			
		||||
    <path fill="none" stroke="#040000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" d="M500,928.8c0,0,0,40.8-245,40.8" />
 | 
			
		||||
    <path d="M581.7,316.2c22.6,0,40.8,18.3,40.8,40.8s-18.3,40.8-40.8,40.8c-22.6,0-40.8-18.3-40.8-40.8S559.1,316.2,581.7,316.2z" />
 | 
			
		||||
    <circle cx="418.3" cy="357.1" r="40.8" />
 | 
			
		||||
    <path d="M500,403.6c0,0-34.8,25.3-81.7,38.9c24.2,63.1,81.7,77.9,81.7,77.9s57.5-14.7,81.7-77.9C534.8,429,500,403.6,500,403.6z" />
 | 
			
		||||
    <path d="M856.6,689.6c0,0-3.3-89,16.6-196.2c39.4,104.7,119.7,164.3,116.8,203.3C930.9,739.3,856.6,689.6,856.6,689.6z" />
 | 
			
		||||
    <path d="M143.4,689.6c0,0,3.3-89-16.6-196.2C87.5,598.1,7.1,657.7,10.1,696.7C69.1,739.3,143.4,689.6,143.4,689.6z" />
 | 
			
		||||
    <path d="M835.4,367.4c0-232.1-184.3-337-335.4-337s-335.4,104.8-335.4,337c-3,24.9-42.3,125.5-42.3,151.4c0,269.7,109.3,450.8,377.8,450.8c268.5,0,377.8-181.1,377.8-450.8C877.8,492.9,838.4,392.2,835.4,367.4z M500,856.9c-119.1,0-264.4-56.5-264.4-265c0-100.2,37.8-148.3,37.8-148.3s-72.5-271.8,132.2-271.8c50.9,0,77.3,48.4,94.4,48.4s50.8-48.4,94.4-48.4c204.7,0,132.2,271.8,132.2,271.8s37.8,48.1,37.8,148.3C764.5,800.4,619.1,856.9,500,856.9z" />
 | 
			
		||||
  </g>
 | 
			
		||||
</svg>
 | 
			
		||||
| 
		 After Width: | Height: | Size: 3.3 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								inspector-vc/src/test/resources/ob30/simple-jwt.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								inspector-vc/src/test/resources/ob30/simple-jwt.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 82 KiB  | 
							
								
								
									
										17
									
								
								inspector-vc/src/test/resources/ob30/simple-jwt.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								inspector-vc/src/test/resources/ob30/simple-jwt.svg
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,17 @@
 | 
			
		||||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
 | 
			
		||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:openbadges="https://purl.imsglobal.org/ob/v3p0" x="0px" y="0px" viewBox="0 0 1000 1000" enable-background="new 0 0 1000 1000" xml:space="preserve">
 | 
			
		||||
    <openbadges:credential verify="eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJ2YyI6eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSIsImh0dHBzOi8vaW1zZ2xvYmFsLmdpdGh1Yi5pby9vcGVuYmFkZ2VzLXNwZWNpZmljYXRpb24vY29udGV4dC5qc29uIl0sImlkIjoiaHR0cDovL2V4YW1wbGUuZWR1L2NyZWRlbnRpYWxzLzM3MzIiLCJ0eXBlIjpbIlZlcmlmaWFibGVDcmVkZW50aWFsIiwiT3BlbkJhZGdlQ3JlZGVudGlhbCJdLCJpc3N1ZXIiOnsiaWQiOiJodHRwczovL2V4YW1wbGUuZWR1L2lzc3VlcnMvNTY1MDQ5IiwidHlwZSI6WyJQcm9maWxlIl0sIm5hbWUiOiJFeGFtcGxlIFVuaXZlcnNpdHkifSwiaXNzdWFuY2VEYXRlIjoiMjAxMC0wMS0wMVQwMDowMDowMFoiLCJuYW1lIjoiRXhhbXBsZSBVbml2ZXJzaXR5IERlZ3JlZSIsImNyZWRlbnRpYWxTdWJqZWN0Ijp7ImlkIjoiZGlkOmV4YW1wbGU6ZWJmZWIxZjcxMmViYzZmMWMyNzZlMTJlYzIxIiwidHlwZSI6WyJBY2hpZXZlbWVudFN1YmplY3QiXX19LCJpc3MiOiJodHRwczovL2V4YW1wbGUuZWR1L2lzc3VlcnMvNTY1MDQ5IiwibmJmIjoxMjYyMzA0MDAwLCJqdGkiOiJodHRwOi8vZXhhbXBsZS5lZHUvY3JlZGVudGlhbHMvMzczMiIsInN1YiI6ImRpZDpleGFtcGxlOmViZmViMWY3MTJlYmM2ZjFjMjc2ZTEyZWMyMSJ9.G7W8od9rSZRsVyk26rXjg_fH2CyUihwNpepd6tWgLt_UHC1vUU0Clox8IicnOSkMyYEqAuNZAdCC9_35i1oUcyj1c076Aa0dsVQ2fFVuQPqXBlyZWcBmo5jqOK6R9NHzRAYXwLRXgrB8gz3lSK55cnHTnMtkpXXcUcHkS5ylWbXCLeOWKoygOCuxRN3N6kP-0HOyuk15PWlnkJ2zEKz2pBtVPaNEydcT0kEtoHFMEWVwqo6rnGV-Ea3M7ssDt3145mcl-DVYLXmBVdT8KoO47QAOBaVMR6k-hgrHNBcdhpI-o6IvLIFsGLgrNvWN67i8Z7Baum1mP-HBpsAigdmIpA"></openbadges:credential>
 | 
			
		||||
    <g>
 | 
			
		||||
        <path fill="none" stroke="#040000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" d="M500,928.8c0,0,0,40.8,245,40.8" />
 | 
			
		||||
        <path fill="none" stroke="#040000" stroke-width="1.9215" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" d="M500,928.8c0,0,40.8,0,285.8,0" />
 | 
			
		||||
        <path fill="none" stroke="#040000" stroke-width="1.9215" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" d="M214.2,928.8c0,0,40.8,0,285.8,0" />
 | 
			
		||||
        <path fill="none" stroke="#040000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" d="M500,928.8c0,0,0,40.8-245,40.8" />
 | 
			
		||||
        <path d="M581.7,316.2c22.6,0,40.8,18.3,40.8,40.8s-18.3,40.8-40.8,40.8c-22.6,0-40.8-18.3-40.8-40.8S559.1,316.2,581.7,316.2z" />
 | 
			
		||||
        <circle cx="418.3" cy="357.1" r="40.8" />
 | 
			
		||||
        <path d="M500,403.6c0,0-34.8,25.3-81.7,38.9c24.2,63.1,81.7,77.9,81.7,77.9s57.5-14.7,81.7-77.9C534.8,429,500,403.6,500,403.6z" />
 | 
			
		||||
        <path d="M856.6,689.6c0,0-3.3-89,16.6-196.2c39.4,104.7,119.7,164.3,116.8,203.3C930.9,739.3,856.6,689.6,856.6,689.6z" />
 | 
			
		||||
        <path d="M143.4,689.6c0,0,3.3-89-16.6-196.2C87.5,598.1,7.1,657.7,10.1,696.7C69.1,739.3,143.4,689.6,143.4,689.6z" />
 | 
			
		||||
        <path d="M835.4,367.4c0-232.1-184.3-337-335.4-337s-335.4,104.8-335.4,337c-3,24.9-42.3,125.5-42.3,151.4c0,269.7,109.3,450.8,377.8,450.8c268.5,0,377.8-181.1,377.8-450.8C877.8,492.9,838.4,392.2,835.4,367.4z M500,856.9c-119.1,0-264.4-56.5-264.4-265c0-100.2,37.8-148.3,37.8-148.3s-72.5-271.8,132.2-271.8c50.9,0,77.3,48.4,94.4,48.4s50.8-48.4,94.4-48.4c204.7,0,132.2,271.8,132.2,271.8s37.8,48.1,37.8,148.3C764.5,800.4,619.1,856.9,500,856.9z" />
 | 
			
		||||
    </g>
 | 
			
		||||
</svg>
 | 
			
		||||
| 
		 After Width: | Height: | Size: 3.4 KiB  | 
@ -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"
 | 
			
		||||
      }
 | 
			
		||||
    ]
 | 
			
		||||
  }
 | 
			
		||||
							
								
								
									
										36
									
								
								inspector-vc/src/test/resources/ob30/simple.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								inspector-vc/src/test/resources/ob30/simple.json
									
									
									
									
									
										Normal file
									
								
							@ -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",
 | 
			
		||||
      "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-09T22:56:28Z",
 | 
			
		||||
        "verificationMethod": "https://example.edu/issuers/565049#key-1",
 | 
			
		||||
        "proofPurpose": "assertionMethod",
 | 
			
		||||
        "proofValue": "z58ieJCh4kN6eE2Vq4TyYURKSC4hWWEK7b75NNUL2taZMhKqwTteuByG1wRoGDdCqqNLW5Gq1diUi4qyZ63tQRtyN"
 | 
			
		||||
      }
 | 
			
		||||
    ]
 | 
			
		||||
  }
 | 
			
		||||
							
								
								
									
										1
									
								
								inspector-vc/src/test/resources/ob30/simple.jwt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								inspector-vc/src/test/resources/ob30/simple.jwt
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1 @@
 | 
			
		||||
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJ2YyI6eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSIsImh0dHBzOi8vaW1zZ2xvYmFsLmdpdGh1Yi5pby9vcGVuYmFkZ2VzLXNwZWNpZmljYXRpb24vY29udGV4dC5qc29uIl0sImlkIjoiaHR0cDovL2V4YW1wbGUuZWR1L2NyZWRlbnRpYWxzLzM3MzIiLCJ0eXBlIjpbIlZlcmlmaWFibGVDcmVkZW50aWFsIiwiT3BlbkJhZGdlQ3JlZGVudGlhbCJdLCJpc3N1ZXIiOnsiaWQiOiJodHRwczovL2V4YW1wbGUuZWR1L2lzc3VlcnMvNTY1MDQ5IiwidHlwZSI6WyJQcm9maWxlIl0sIm5hbWUiOiJFeGFtcGxlIFVuaXZlcnNpdHkifSwiaXNzdWFuY2VEYXRlIjoiMjAxMC0wMS0wMVQwMDowMDowMFoiLCJuYW1lIjoiRXhhbXBsZSBVbml2ZXJzaXR5IERlZ3JlZSIsImNyZWRlbnRpYWxTdWJqZWN0Ijp7ImlkIjoiZGlkOmV4YW1wbGU6ZWJmZWIxZjcxMmViYzZmMWMyNzZlMTJlYzIxIiwidHlwZSI6WyJBY2hpZXZlbWVudFN1YmplY3QiXX19LCJpc3MiOiJodHRwczovL2V4YW1wbGUuZWR1L2lzc3VlcnMvNTY1MDQ5IiwibmJmIjoxMjYyMzA0MDAwLCJqdGkiOiJodHRwOi8vZXhhbXBsZS5lZHUvY3JlZGVudGlhbHMvMzczMiIsInN1YiI6ImRpZDpleGFtcGxlOmViZmViMWY3MTJlYmM2ZjFjMjc2ZTEyZWMyMSJ9.G7W8od9rSZRsVyk26rXjg_fH2CyUihwNpepd6tWgLt_UHC1vUU0Clox8IicnOSkMyYEqAuNZAdCC9_35i1oUcyj1c076Aa0dsVQ2fFVuQPqXBlyZWcBmo5jqOK6R9NHzRAYXwLRXgrB8gz3lSK55cnHTnMtkpXXcUcHkS5ylWbXCLeOWKoygOCuxRN3N6kP-0HOyuk15PWlnkJ2zEKz2pBtVPaNEydcT0kEtoHFMEWVwqo6rnGV-Ea3M7ssDt3145mcl-DVYLXmBVdT8KoO47QAOBaVMR6k-hgrHNBcdhpI-o6IvLIFsGLgrNvWN67i8Z7Baum1mP-HBpsAigdmIpA
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user