Filling out embedded verifiable credential inspector.

This commit is contained in:
Miles Lyon 2022-08-12 15:04:53 -04:00
parent 0545dbf9e3
commit 79b0932529

View File

@ -1,13 +1,164 @@
package org.oneedtech.inspect.vc; package org.oneedtech.inspect.vc;
import static java.lang.Boolean.TRUE;
import static org.oneedtech.inspect.core.probe.RunContext.Key.*;
import static org.oneedtech.inspect.util.json.ObjectMapperCache.Config.DEFAULT;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional;
import org.oneedtech.inspect.core.SubInspector; import org.oneedtech.inspect.core.SubInspector;
import org.oneedtech.inspect.core.probe.GeneratedObject;
import org.oneedtech.inspect.core.probe.Probe;
import org.oneedtech.inspect.core.probe.RunContext;
import org.oneedtech.inspect.core.probe.json.JsonPathEvaluator;
import org.oneedtech.inspect.core.probe.json.JsonSchemaProbe;
import org.oneedtech.inspect.core.report.Report; import org.oneedtech.inspect.core.report.Report;
import org.oneedtech.inspect.core.report.ReportItems;
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.Resource;
import org.oneedtech.inspect.util.resource.UriResource;
import org.oneedtech.inspect.util.resource.context.ResourceContext;
import org.oneedtech.inspect.vc.Credential.Type;
import org.oneedtech.inspect.vc.probe.CredentialParseProbe;
import org.oneedtech.inspect.vc.probe.InlineJsonSchemaProbe;
import org.oneedtech.inspect.vc.probe.SignatureVerifierProbe;
import org.oneedtech.inspect.vc.probe.TypePropertyProbe;
public class EmbeddedVCInspector { import com.fasterxml.jackson.databind.JsonNode;
//TODO: @Miles Need to confirm with Markus, but this feels like an embedded inspector with some different rules (type, etc..) like endorsements import com.fasterxml.jackson.databind.ObjectMapper;
/**
* An inspector for EndorsementCredential objects.
* @author mgylling
*/
public class EmbeddedVCInspector extends VCInspector implements SubInspector {
protected <B extends VCInspector.Builder<?>> EmbeddedVCInspector(B builder) {
super(builder);
}
@Override
public Report run(Resource resource, Map<String, GeneratedObject> parentObjects) {
/*
* The resource param is the top-level credential that embeds the endorsement, we
* expect parentObjects to provide a pointer to the JsonNode we should check
*/
Credential verifiableCredential = (Credential) parentObjects.get(VC_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(VC_KEY, verifiableCredential)
.build();
List<ReportItems> accumulator = new ArrayList<>();
int probeCount = 0;
try {
//detect type (png, svg, json, jwt) and extract json data
probeCount++;
accumulator.add(new CredentialParseProbe().run(resource, ctx));
if(broken(accumulator)) return abort(ctx, accumulator, probeCount);
//we expect the above to place a generated object in the context
Credential crd = ctx.getGeneratedObject(Credential.ID);
//type property
probeCount++;
accumulator.add(new TypePropertyProbe(Type.ClrCredential).run(crd.getJson(), ctx));
if(broken(accumulator)) return abort(ctx, accumulator, probeCount);
//canonical schema and inline schema
SchemaKey schema = crd.getSchemaKey().orElseThrow();
for(Probe<JsonNode> probe : List.of(new JsonSchemaProbe(schema), new InlineJsonSchemaProbe(schema))) {
probeCount++;
accumulator.add(probe.run(crd.getJson(), ctx));
if(broken(accumulator)) return abort(ctx, accumulator, probeCount);
}
//signatures, proofs
probeCount++;
if(crd.getJwt().isPresent()){
//The credential originally contained in a JWT, validate the jwt and external proof.
accumulator.add(new SignatureVerifierProbe().run(crd, ctx));
} else {
//The credential not contained in a jwt, must have an internal proof.
//TODO: @Miles Need to fix the issuer, Same as with outer CLR
//Swap -> "verificationMethod": "https://example.edu/issuers/565049#z6MkwA1498JfoCS3y4y3zggBDAosQEoCi5gsYH2PMXh1cFWK",
//To be like -> "verificationMethod": "did:key:z6MkkUD3J14nkYzn46QeuaVSnp7dF85QJKwKvJvfsjx79aXj",
//...but also work properly which old record seems not be doing...
/*
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);
if(newID.isPresent()) {
//TODO resource.type
return this.run(
new UriResource(new URI(newID.get()))
.setContext(new ResourceContext(REFRESHED, TRUE)));
}
}
} catch (Exception e) {
//TODO: Need to figure out the issue here.
//accumulator.add(onProbeException(Probe.ID.NO_UNCAUGHT_EXCEPTIONS, resource, e));
}
return new Report(ctx, new ReportItems(accumulator), probeCount);
}
@Override
public <R extends Resource> Report run(R resource) {
throw new IllegalStateException("must use #run(resource, map)");
}
public static class Builder extends VCInspector.Builder<EmbeddedVCInspector.Builder> {
@SuppressWarnings("unchecked")
@Override
public EmbeddedVCInspector build() {
return new EmbeddedVCInspector(this);
}
}
public static final String VC_KEY = "VC_KEY";
private static final String REFRESHED = "is.refreshed.credential";
/**
* 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) {
JsonNode refreshServiceNode = crd.getJson().get("refreshService");
if(refreshServiceNode != null) {
JsonNode serviceTypeNode = refreshServiceNode.get("type");
if(serviceTypeNode != null && serviceTypeNode.asText().equals("1EdTechCredentialRefresh")) {
JsonNode serviceURINode = refreshServiceNode.get("id");
if(serviceURINode != null) {
return Optional.of(serviceURINode.asText());
}
}
}
return Optional.empty();
}
} }