diff --git a/inspector-vc/pom.xml b/inspector-vc/pom.xml
index 6881751..396f3d5 100644
--- a/inspector-vc/pom.xml
+++ b/inspector-vc/pom.xml
@@ -10,6 +10,31 @@
org.1edtech
inspector-core
+
+
+ org.bouncycastle
+ bcpkix-jdk15on
+ 1.58
+
+
+ com.auth0
+ auth0
+ 1.20.0
+
+
+ com.auth0
+ jwks-rsa
+ 0.12.0
+
+
+ com.auth0
+ java-jwt
+ 3.10.3
+
+
+ org.springframework
+ spring-core
+ 5.0.12.RELEASE
\ No newline at end of file
diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Credential.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Credential.java
index 4efcf5a..3e73655 100644
--- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Credential.java
+++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/Credential.java
@@ -52,6 +52,10 @@ public class Credential extends GeneratedObject {
public Credential.Type getCredentialType() {
return credentialType;
}
+
+ public String getJwt() {
+ return jwt;
+ }
/**
* Get the canonical schema for this credential if such exists.
diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/OB30Inspector.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/OB30Inspector.java
index 71ca34f..da95e80 100644
--- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/OB30Inspector.java
+++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/OB30Inspector.java
@@ -6,6 +6,7 @@ import static org.oneedtech.inspect.core.report.ReportUtil.onProbeException;
import static org.oneedtech.inspect.util.json.ObjectMapperCache.Config.DEFAULT;
import static org.oneedtech.inspect.vc.EndorsementInspector.ENDORSEMENT_KEY;
import static org.oneedtech.inspect.vc.util.JsonNodeUtil.getEndorsements;
+import static com.google.common.base.Strings.isNullOrEmpty;
import java.net.URI;
import java.util.ArrayList;
@@ -105,10 +106,12 @@ public class OB30Inspector extends VCInspector {
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);
+ //If this credential was originally contained in a JWT we must validate the jwt and external proof.
+ if(!isNullOrEmpty(crd.getJwt())){
+ probeCount++;
+ accumulator.add(new SignatureVerifierProbe().run(crd, ctx));
+ if(broken(accumulator)) return abort(ctx, accumulator, probeCount);
+ }
//verify proofs TODO @Miles
probeCount++;
diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/CredentialTypeProbe.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/CredentialTypeProbe.java
index bf59280..535c9c0 100644
--- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/CredentialTypeProbe.java
+++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/CredentialTypeProbe.java
@@ -49,18 +49,13 @@ public class CredentialTypeProbe extends Probe {
if(type.isPresent()) {
resource.setType(type.get());
- //TODO: Refactor to return the entire credential so we can include optional encoded JWT.
if(type.get() == ResourceType.PNG) {
- //crd = new Credential(resource, fromPNG(resource, context));
crd = fromPNG(resource, context);
} else if(type.get() == ResourceType.SVG) {
- //crd = new Credential(resource, fromSVG(resource, context));
crd = fromSVG(resource, context);
} else if(type.get() == ResourceType.JSON) {
- //crd = new Credential(resource, fromJson(resource, context));
crd = fromJson(resource, context);
} else if(type.get() == ResourceType.JWT) {
- //crd = new Credential(resource, fromJWT(resource, context));
crd = fromJWT(resource, context);
}
}
diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/SignatureVerifierProbe.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/SignatureVerifierProbe.java
index d9961fa..08ad567 100644
--- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/SignatureVerifierProbe.java
+++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/SignatureVerifierProbe.java
@@ -1,9 +1,30 @@
package org.oneedtech.inspect.vc.probe;
+import static com.google.common.base.Strings.isNullOrEmpty;
+
+import java.math.BigInteger;
+import java.nio.charset.StandardCharsets;
+import java.security.KeyFactory;
+import java.security.PublicKey;
+import java.security.interfaces.RSAPublicKey;
+import java.security.spec.RSAPublicKeySpec;
+import java.util.List;
+import java.util.Base64;
+import java.util.Base64.Decoder;
+
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.springframework.util.Base64Utils;
+
+import com.auth0.jwt.JWT;
+import com.auth0.jwt.JWTVerifier;
+import com.auth0.jwt.algorithms.Algorithm;
+import com.auth0.jwt.interfaces.DecodedJWT;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.common.base.Splitter;
/**
* A Probe that verifies credential signatures
@@ -17,11 +38,56 @@ public class SignatureVerifierProbe extends Probe {
@Override
public ReportItems run(Credential crd, RunContext ctx) throws Exception {
-
- //TODO @Miles -- if sigs fail, report OutCome.Fatal
+ try {
+ testingSignatureValidationCode(crd);
+ } catch (Exception e) {
+ return fatal("Error verifying jwt signature: " + e.getMessage(), ctx);
+ }
return success(ctx);
}
+
+ private void testingSignatureValidationCode(Credential crd) throws Exception {
+ DecodedJWT decodedJwt = null;
+ String jwt = crd.getJwt();
+ if(isNullOrEmpty(jwt)) throw new IllegalArgumentException("invalid jwt");
+ List parts = Splitter.on('.').splitToList(jwt);
+ if(parts.size() != 3) throw new IllegalArgumentException("invalid jwt");
+
+ final Decoder decoder = Base64.getUrlDecoder();
+ String joseHeader = new String(decoder.decode(parts.get(0)));
+
+ ObjectMapper mapper = new ObjectMapper();
+ JsonNode headerObj = mapper.readTree(joseHeader);
+
+ //MUST be "RS256"
+
+ //Option 1, fetch directly from header
+ JsonNode jwk = headerObj.get("jwk");
+
+ //Option 2, fetch from a hosting url
+ JsonNode kid = headerObj.get("kid");
+
+ if(jwk == null && kid == null) { throw new Exception("asdf"); }
+ if(kid != null){
+ //TODO @Miles load jwk JsonNode from url and do the rest the same below. Need to set up testing.
+ String kidUrl = kid.textValue();
+ }
+
+ //Clean up may be required. Currently need to cleanse extra double quoting.
+ String modulusString = jwk.get("n").textValue();
+ String exponentString = jwk.get("e").textValue();
+
+ BigInteger modulus = new BigInteger(1, org.springframework.util.Base64Utils.decodeFromUrlSafeString(modulusString));
+ BigInteger exponent = new BigInteger(1, org.springframework.util.Base64Utils.decodeFromUrlSafeString(exponentString));
+
+ PublicKey pub = KeyFactory.getInstance("RSA").generatePublic(new RSAPublicKeySpec(modulus, exponent));
+
+ Algorithm algorithm = Algorithm.RSA256((RSAPublicKey)pub, null);
+ JWTVerifier verifier = JWT.require(algorithm)
+ .build(); //Reusable verifier instance
+ decodedJwt = verifier.verify(jwt);
+ }
public static final String ID = SignatureVerifierProbe.class.getSimpleName();