Added JWT signature verification

This commit is contained in:
Xavi Aracil 2022-12-09 14:27:09 +01:00
parent dbee83068f
commit a4d8a1692f
2 changed files with 137 additions and 1 deletions

View File

@ -35,6 +35,7 @@ import org.oneedtech.inspect.vc.probe.ExpirationProbe;
import org.oneedtech.inspect.vc.probe.IssuanceProbe; import org.oneedtech.inspect.vc.probe.IssuanceProbe;
import org.oneedtech.inspect.vc.probe.TypePropertyProbe; import org.oneedtech.inspect.vc.probe.TypePropertyProbe;
import org.oneedtech.inspect.vc.probe.VerificationDependenciesProbe; import org.oneedtech.inspect.vc.probe.VerificationDependenciesProbe;
import org.oneedtech.inspect.vc.probe.VerificationJWTProbe;
import org.oneedtech.inspect.vc.probe.validation.ValidationPropertyProbeFactory; import org.oneedtech.inspect.vc.probe.validation.ValidationPropertyProbeFactory;
import org.oneedtech.inspect.vc.util.CachingDocumentLoader; import org.oneedtech.inspect.vc.util.CachingDocumentLoader;
@ -150,12 +151,20 @@ public class OB20Inspector extends Inspector {
} }
// verification and revocation // verification and revocation
for(Probe<JsonLdGeneratedObject> probe : List.of(new VerificationDependenciesProbe(assertion.getId()), new AssertionRevocationListProbe(assertion.getId()))) { for(Probe<JsonLdGeneratedObject> probe : List.of(new VerificationDependenciesProbe(assertion.getId()),
new AssertionRevocationListProbe(assertion.getId()))) {
probeCount++; probeCount++;
accumulator.add(probe.run(jsonLdGeneratedObject, ctx)); accumulator.add(probe.run(jsonLdGeneratedObject, ctx));
if(broken(accumulator)) return abort(ctx, accumulator, probeCount); if(broken(accumulator)) return abort(ctx, accumulator, probeCount);
} }
// JWS verification
if (assertion.getJwt().isPresent()) {
probeCount++;
accumulator.add(new VerificationJWTProbe(assertion.getJwt().get()).run(jsonLdGeneratedObject, ctx));
if(broken(accumulator)) return abort(ctx, accumulator, probeCount);
}
} catch (Exception e) { } catch (Exception e) {
accumulator.add(onProbeException(Probe.ID.NO_UNCAUGHT_EXCEPTIONS, resource, e)); accumulator.add(onProbeException(Probe.ID.NO_UNCAUGHT_EXCEPTIONS, resource, e));

View File

@ -0,0 +1,127 @@
package org.oneedtech.inspect.vc.probe;
import java.net.URI;
import java.net.URISyntaxException;
import java.security.KeyFactory;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import java.util.List;
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.report.ReportItems;
import org.oneedtech.inspect.util.resource.UriResource;
import org.oneedtech.inspect.vc.jsonld.JsonLdGeneratedObject;
import org.oneedtech.inspect.vc.jsonld.probe.JsonLDCompactionProve;
import org.oneedtech.inspect.vc.util.CachingDocumentLoader;
import org.oneedtech.inspect.vc.util.JsonNodeUtil;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.nimbusds.jose.JOSEException;
import com.nimbusds.jose.JWSObject;
import com.nimbusds.jose.JWSVerifier;
import com.nimbusds.jose.crypto.RSASSAVerifier;
import foundation.identity.jsonld.ConfigurableDocumentLoader;
/**
* Recipient Verification probe for Open Badges 2.0
* Maps to "VERIFY_JWS" task in python implementation
* @author xaracil
*/
public class VerificationJWTProbe extends Probe<JsonLdGeneratedObject> {
final String jwt;
public VerificationJWTProbe(String jwt) {
super(ID);
this.jwt = jwt;
}
@Override
public ReportItems run(JsonLdGeneratedObject assertion, RunContext ctx) throws Exception {
ObjectMapper mapper = (ObjectMapper) ctx.get(Key.JACKSON_OBJECTMAPPER);
JsonNode assertionNode = (mapper).readTree(assertion.getJson());
// get badge from assertion
UriResource badgeUriResource = resolveUriResource(ctx, assertionNode.get("badge").asText().strip());
JsonLdGeneratedObject badgeObject = (JsonLdGeneratedObject) ctx.getGeneratedObject(
JsonLDCompactionProve.getId(badgeUriResource));
JsonNode badgeNode = ((ObjectMapper) ctx.get(Key.JACKSON_OBJECTMAPPER))
.readTree(badgeObject.getJson());
// get issuer from badge
UriResource issuerUriResource = resolveUriResource(ctx, badgeNode.get("issuer").asText().strip());
JsonLdGeneratedObject issuerObject = (JsonLdGeneratedObject) ctx.getGeneratedObject(
JsonLDCompactionProve.getId(issuerUriResource));
JsonNode issuerNode = ((ObjectMapper) ctx.get(Key.JACKSON_OBJECTMAPPER))
.readTree(issuerObject.getJson());
// get verification from assertion
JsonNode creatorIdNode = assertionNode.get("verification").get("creator");
String creatorId = null;
if (creatorIdNode != null) {
creatorId = creatorIdNode.asText().strip();
} else {
// If not present, verifiers will check public key(s) declared in the referenced issuer Profile.
creatorId = issuerNode.get("publicKeyPem").asText().strip();
}
// get creator from id
UriResource creatorUriResource = resolveUriResource(ctx, creatorId);
JsonLdGeneratedObject creatorObject = (JsonLdGeneratedObject) ctx.getGeneratedObject(
JsonLDCompactionProve.getId(creatorUriResource));
JsonNode creatorNode = ((ObjectMapper) ctx.get(Key.JACKSON_OBJECTMAPPER))
.readTree(creatorObject.getJson());
// verify key ownership
String keyId = creatorNode.get("id").asText().strip();
List<String> issuerKeys = JsonNodeUtil.asStringList(issuerNode.get("publicKey"));
if (!issuerKeys.contains(keyId)) {
return error("Assertion signed by a key " + keyId + " other than those authorized by issuer profile", ctx);
}
String publicKeyPem = creatorNode.get("publicKeyPem").asText().strip();
// verify signature
publicKeyPem = publicKeyPem.replace("-----BEGIN PUBLIC KEY-----", "");
publicKeyPem = publicKeyPem.replace("-----END PUBLIC KEY-----", "");
publicKeyPem = publicKeyPem.replace("\n", "");
byte[] encodedPb = Base64.getDecoder().decode(publicKeyPem);
X509EncodedKeySpec keySpecPb = new X509EncodedKeySpec(encodedPb);
RSAPublicKey publicKey = (RSAPublicKey) KeyFactory.getInstance("RSA").generatePublic(keySpecPb);
JWSObject jwsObject = JWSObject.parse(jwt);
JWSVerifier verifier = new RSASSAVerifier(publicKey);
try {
if (!jwsObject.verify(verifier)) {
return error("Signature for node " + assertionNode.get("id") + " failed verification ", ctx);
}
} catch (JOSEException e) {
return fatal("Signature for node " + assertionNode.get("id") + " failed verification " + e.getLocalizedMessage(), ctx);
}
return success(ctx);
}
protected UriResource resolveUriResource(RunContext ctx, String url) throws URISyntaxException {
URI uri = new URI(url);
UriResource initialUriResource = new UriResource(uri);
UriResource uriResource = initialUriResource;
// check if uri points to a local resource
if (ctx.get(Key.JSON_DOCUMENT_LOADER) instanceof ConfigurableDocumentLoader) {
if (ConfigurableDocumentLoader.getDefaultHttpLoader() instanceof CachingDocumentLoader.HttpLoader) {
URI resolvedUri = ((CachingDocumentLoader.HttpLoader) ConfigurableDocumentLoader.getDefaultHttpLoader()).resolve(uri);
uriResource = new UriResource(resolvedUri);
}
}
return uriResource;
}
private static final List<String> allowedTypes = List.of("id", "email", "url", "telephone");
public static final String ID = VerificationJWTProbe.class.getSimpleName();
}