Merge pull request #76 from imsglc/feature/did_web_support
Added did:web verification method
This commit is contained in:
		
						commit
						c808749cc4
					
				@ -2,6 +2,8 @@ package org.oneedtech.inspect.vc.probe;
 | 
			
		||||
 | 
			
		||||
import java.io.StringReader;
 | 
			
		||||
import java.net.URI;
 | 
			
		||||
import java.net.URLEncoder;
 | 
			
		||||
import java.nio.charset.Charset;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.Optional;
 | 
			
		||||
 | 
			
		||||
@ -20,8 +22,11 @@ import com.apicatalog.multicodec.Multicodec.Codec;
 | 
			
		||||
 | 
			
		||||
import info.weboftrust.ldsignatures.LdProof;
 | 
			
		||||
import info.weboftrust.ldsignatures.verifier.Ed25519Signature2020LdVerifier;
 | 
			
		||||
import jakarta.json.JsonArray;
 | 
			
		||||
import jakarta.json.JsonObject;
 | 
			
		||||
import jakarta.json.JsonString;
 | 
			
		||||
import jakarta.json.JsonStructure;
 | 
			
		||||
import jakarta.json.JsonValue;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A Probe that verifies a credential's embedded proof.
 | 
			
		||||
@ -41,15 +46,17 @@ public class EmbeddedProofProbe extends Probe<VerifiableCredential> {
 | 
			
		||||
	@Override
 | 
			
		||||
	public ReportItems run(VerifiableCredential crd, RunContext ctx) throws Exception {
 | 
			
		||||
 | 
			
		||||
		W3CVCHolder credentiaHolder = new W3CVCHolder(com.danubetech.verifiablecredentials.VerifiableCredential.fromJson(new StringReader(crd.getJson().toString())));
 | 
			
		||||
		W3CVCHolder credentialHolder = new W3CVCHolder(com.danubetech.verifiablecredentials.VerifiableCredential
 | 
			
		||||
				.fromJson(new StringReader(crd.getJson().toString())));
 | 
			
		||||
 | 
			
		||||
		List<LdProof> proofs = credentiaHolder.getProofs();
 | 
			
		||||
		List<LdProof> proofs = credentialHolder.getProofs();
 | 
			
		||||
		if (proofs == null || proofs.size() == 0) {
 | 
			
		||||
			return error("The verifiable credential is missing a proof.", ctx);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// get proof of standard type and purpose
 | 
			
		||||
		Optional<LdProof> selectedProof = proofs.stream().filter(proof -> proof.isType("Ed25519Signature2020") && proof.getProofPurpose().equals("assertionMethod"))
 | 
			
		||||
		Optional<LdProof> selectedProof = proofs.stream().filter(
 | 
			
		||||
				proof -> proof.isType("Ed25519Signature2020") && proof.getProofPurpose().equals("assertionMethod"))
 | 
			
		||||
				.findFirst();
 | 
			
		||||
 | 
			
		||||
		if (!selectedProof.isPresent()) {
 | 
			
		||||
@ -75,8 +82,10 @@ public class EmbeddedProofProbe extends Probe<VerifiableCredential> {
 | 
			
		||||
		//
 | 
			
		||||
		// [controller]#[publicKeyMultibase]
 | 
			
		||||
		// did:key:[publicKeyMultibase]
 | 
			
		||||
		// did:web:[url-encoded domain-name][:path]*
 | 
			
		||||
		// http/s://[location of a Ed25519VerificationKey2020 document]
 | 
			
		||||
		// http/s://[location of a controller document with a 'verificationMethod' with a Ed25519VerificationKey2020]
 | 
			
		||||
		// http/s://[location of a controller document with a 'verificationMethod' with
 | 
			
		||||
		// a Ed25519VerificationKey2020]
 | 
			
		||||
 | 
			
		||||
		String publicKeyMultibase;
 | 
			
		||||
		String controller = null;
 | 
			
		||||
@ -92,12 +101,98 @@ public class EmbeddedProofProbe extends Probe<VerifiableCredential> {
 | 
			
		||||
			} else if (method.getScheme().equals("did")) {
 | 
			
		||||
				if (method.getSchemeSpecificPart().startsWith("key:")) {
 | 
			
		||||
					publicKeyMultibase = method.getSchemeSpecificPart().substring("key:".length());
 | 
			
		||||
				} else if (method.getSchemeSpecificPart().startsWith("web:")) {
 | 
			
		||||
					String methodSpecificId = method.getRawSchemeSpecificPart().substring("web:".length());
 | 
			
		||||
 | 
			
		||||
					// read algorithm at https://w3c-ccg.github.io/did-method-web/#read-resolve.
 | 
			
		||||
					// Steps in comments
 | 
			
		||||
 | 
			
		||||
					// 1. Replace ":" with "/" in the method specific identifier to obtain the fully
 | 
			
		||||
					// qualified domain name and optional path.
 | 
			
		||||
					methodSpecificId = methodSpecificId.replaceAll(":", "/");
 | 
			
		||||
 | 
			
		||||
					// 2. If the domain contains a port percent decode the colon.
 | 
			
		||||
					String portPercentEncoded = URLEncoder.encode(":", Charset.forName("UTF-8"));
 | 
			
		||||
					int index = methodSpecificId.indexOf(portPercentEncoded);
 | 
			
		||||
					if (index >= 0 && index < methodSpecificId.indexOf("/")) {
 | 
			
		||||
						methodSpecificId = methodSpecificId.replace(portPercentEncoded, ":");
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
					// 3. Generate an HTTPS URL to the expected location of the DID document by
 | 
			
		||||
					// prepending https://.
 | 
			
		||||
					URI uri = new URI("https://" + methodSpecificId);
 | 
			
		||||
 | 
			
		||||
					// 4. If no path has been specified in the URL, append /.well-known.
 | 
			
		||||
					if (uri.getPath() == null) {
 | 
			
		||||
						uri = uri.resolve("/well-known");
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
					// 5. Append /did.json to complete the URL.
 | 
			
		||||
					uri = uri.resolve(uri.getPath() + "/did.json");
 | 
			
		||||
 | 
			
		||||
					// 6. Perform an HTTP GET request to the URL using an agent that can
 | 
			
		||||
					// successfully negotiate a secure HTTPS connection, which enforces the security
 | 
			
		||||
					// requirements as described in 2.6 Security and privacy considerations.
 | 
			
		||||
					// 7. When performing the DNS resolution during the HTTP GET request, the client
 | 
			
		||||
					// SHOULD utilize [RFC8484] in order to prevent tracking of the identity being
 | 
			
		||||
					// resolved.
 | 
			
		||||
					Optional<JsonStructure> keyStructure;
 | 
			
		||||
					try {
 | 
			
		||||
						Document keyDocument = credentialHolder.getCredential().getDocumentLoader().loadDocument(uri,
 | 
			
		||||
								new DocumentLoaderOptions());
 | 
			
		||||
						keyStructure = keyDocument.getJsonContent();
 | 
			
		||||
					} catch (Exception e) {
 | 
			
		||||
						return error("Key document not found at " + method + ". URI: " + uri
 | 
			
		||||
								+ " doesn't return a valid document. Reason: " + e.getMessage() + " ", ctx);
 | 
			
		||||
					}
 | 
			
		||||
					if (keyStructure.isEmpty()) {
 | 
			
		||||
						return error("Key document not found at " + method + ". URI: " + uri
 | 
			
		||||
								+ " doesn't return a valid document. Reason: The document is empty.", ctx);
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
					// check did in "assertionMethod"
 | 
			
		||||
					JsonArray assertionMethod = keyStructure.get().asJsonObject()
 | 
			
		||||
							.getJsonArray("assertionMethod");
 | 
			
		||||
					if (assertionMethod == null) {
 | 
			
		||||
						return error("Document doesn't have a list of assertion methods at URI: " + uri, ctx);
 | 
			
		||||
					} else {
 | 
			
		||||
						Boolean anyMatch = false;
 | 
			
		||||
						for(int i = 0; i < assertionMethod.size(); i++) {
 | 
			
		||||
							String assertionMethodValue = assertionMethod.getString(i);
 | 
			
		||||
							if (assertionMethodValue.equals(method.toString())) {
 | 
			
		||||
								anyMatch = true;
 | 
			
		||||
								break;
 | 
			
		||||
							}
 | 
			
		||||
						}
 | 
			
		||||
						if (!anyMatch) {
 | 
			
		||||
							return error("Assertion method " + method + " not found in DID document.", ctx);
 | 
			
		||||
						}	
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
					// get keys from "verificationMethod"
 | 
			
		||||
					JsonArray keyVerificationMethod = keyStructure.get().asJsonObject()
 | 
			
		||||
							.getJsonArray("verificationMethod");
 | 
			
		||||
					if (keyVerificationMethod == null) {
 | 
			
		||||
						return error("Document doesn't have a list of verification methods at URI: " + uri, ctx);
 | 
			
		||||
					}
 | 
			
		||||
					Optional<JsonValue> verificationMethodMaybe = keyVerificationMethod.stream()
 | 
			
		||||
							.filter(n -> n.asJsonObject().getString("id").equals(method.toString()))
 | 
			
		||||
							.findFirst();
 | 
			
		||||
					if (verificationMethodMaybe.isEmpty()) {
 | 
			
		||||
						return error("Verification method " + method + " not found in DID document.", ctx);
 | 
			
		||||
					}
 | 
			
		||||
					JsonObject verificationMethod = verificationMethodMaybe.get().asJsonObject();
 | 
			
		||||
					// assuming a Ed25519VerificationKey2020 document
 | 
			
		||||
					controller = verificationMethod.getString("controller");
 | 
			
		||||
					publicKeyMultibase = verificationMethod.getString("publicKeyMultibase");
 | 
			
		||||
 | 
			
		||||
				} else {
 | 
			
		||||
					return error("Unknown verification method: " + method, ctx);
 | 
			
		||||
				}
 | 
			
		||||
			} else if (method.getScheme().equals("http") || method.getScheme().equals("https")) {
 | 
			
		||||
				try {
 | 
			
		||||
					Document keyDocument = credentiaHolder.getCredential().getDocumentLoader().loadDocument(method, new DocumentLoaderOptions());
 | 
			
		||||
					Document keyDocument = credentialHolder.getCredential().getDocumentLoader().loadDocument(method,
 | 
			
		||||
							new DocumentLoaderOptions());
 | 
			
		||||
					Optional<JsonStructure> keyStructure = keyDocument.getJsonContent();
 | 
			
		||||
					if (keyStructure.isEmpty()) {
 | 
			
		||||
						return error("Key document not found at " + method, ctx);
 | 
			
		||||
@ -106,7 +201,8 @@ public class EmbeddedProofProbe extends Probe<VerifiableCredential> {
 | 
			
		||||
					// First look for a Ed25519VerificationKey2020 document
 | 
			
		||||
					controller = keyStructure.get().asJsonObject().getString("controller");
 | 
			
		||||
					if (StringUtils.isBlank(controller)) {
 | 
			
		||||
						// Then look for a controller document (e.g. DID Document) with a 'verificationMethod'
 | 
			
		||||
						// Then look for a controller document (e.g. DID Document) with a
 | 
			
		||||
						// 'verificationMethod'
 | 
			
		||||
						// that is a Ed25519VerificationKey2020 document
 | 
			
		||||
						JsonObject keyVerificationMethod = keyStructure.get().asJsonObject()
 | 
			
		||||
								.getJsonObject("verificationMethod");
 | 
			
		||||
@ -140,8 +236,9 @@ public class EmbeddedProofProbe extends Probe<VerifiableCredential> {
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (controller != null) {
 | 
			
		||||
			if (!controller.equals(credentiaHolder.getCredential().getIssuer().toString())) {
 | 
			
		||||
				return error("Key controller does not match issuer: " + credentiaHolder.getCredential().getIssuer(), ctx);
 | 
			
		||||
			if (!controller.equals(credentialHolder.getCredential().getIssuer().toString())) {
 | 
			
		||||
				return error("Key controller does not match issuer: " + credentialHolder.getCredential().getIssuer(),
 | 
			
		||||
						ctx);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
@ -151,7 +248,7 @@ public class EmbeddedProofProbe extends Probe<VerifiableCredential> {
 | 
			
		||||
		Ed25519Signature2020LdVerifier verifier = new Ed25519Signature2020LdVerifier(publicKey);
 | 
			
		||||
 | 
			
		||||
		try {
 | 
			
		||||
			boolean verify = verifier.verify(credentiaHolder.getCredential(), proof);
 | 
			
		||||
			boolean verify = verifier.verify(credentialHolder.getCredential(), proof);
 | 
			
		||||
			if (!verify) {
 | 
			
		||||
				return error("Embedded proof verification failed.", ctx);
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
@ -44,9 +44,18 @@ public class OB30Tests {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Test
 | 
			
		||||
	void testSimpleDidMethodJsonValid() {
 | 
			
		||||
	void testSimpleDidKeyMethodJsonValid() {
 | 
			
		||||
		assertDoesNotThrow(()->{
 | 
			
		||||
			Report report = validator.run(Samples.OB30.JSON.SIMPLE_DID_METHOD_JSON.asFileResource());
 | 
			
		||||
			Report report = validator.run(Samples.OB30.JSON.SIMPLE_DID_KEY_METHOD_JSON.asFileResource());
 | 
			
		||||
			if(verbose) PrintHelper.print(report, true);
 | 
			
		||||
			assertValid(report);
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Test
 | 
			
		||||
	void testSimpleDidWebMethodJsonValid() {
 | 
			
		||||
		assertDoesNotThrow(()->{
 | 
			
		||||
			Report report = validator.run(Samples.OB30.JSON.SIMPLE_DID_WEB_METHOD_JSON.asFileResource());
 | 
			
		||||
			if(verbose) PrintHelper.print(report, true);
 | 
			
		||||
			assertValid(report);
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
@ -11,7 +11,8 @@ public class Samples {
 | 
			
		||||
		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_DID_METHOD_JSON = new Sample("ob30/simple-did-method.json", true);
 | 
			
		||||
			public final static Sample SIMPLE_DID_KEY_METHOD_JSON = new Sample("ob30/simple-did-key-method.json", true);
 | 
			
		||||
			public final static Sample SIMPLE_DID_WEB_METHOD_JSON = new Sample("ob30/simple-did-web-method.json", true);
 | 
			
		||||
			public final static Sample SIMPLE_MULTIPLE_PROOF_JSON = new Sample("ob30/simple-multiple-proofs.json", true);
 | 
			
		||||
			public final static Sample SIMPLE_JSON_NOPROOF = new Sample("ob30/simple-noproof.json", false);
 | 
			
		||||
			public final static Sample SIMPLE_JSON_UNKNOWN_TYPE = new Sample("ob30/simple-err-type.json", false);
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,47 @@
 | 
			
		||||
{
 | 
			
		||||
  "@context": [
 | 
			
		||||
    "https://www.w3.org/2018/credentials/v1",
 | 
			
		||||
    "https://purl.imsglobal.org/spec/ob/v3p0/context.json",
 | 
			
		||||
    "https://purl.imsglobal.org/spec/ob/v3p0/extensions.json",
 | 
			
		||||
    "https://w3id.org/security/suites/ed25519-2020/v1"
 | 
			
		||||
  ],
 | 
			
		||||
  "id": "http://dc.1edtech.org/wellspring2022/wellspring-portal/credentials/5d7b7ff6-b1d5-47d5-83df-faf22533ba8f",
 | 
			
		||||
  "type": [
 | 
			
		||||
    "VerifiableCredential",
 | 
			
		||||
    "AchievementCredential"
 | 
			
		||||
  ],
 | 
			
		||||
  "issuer": {
 | 
			
		||||
    "id": "did:web:dc.1edtech.org:wellspring2022:wellspring-portal:org:7ad80b28-4f3f-414d-85ec-6c0684344e5c",
 | 
			
		||||
    "type": "Profile",
 | 
			
		||||
    "name": "Wellspring School"
 | 
			
		||||
  },
 | 
			
		||||
  "awardedDate": "2023-05-16T11:27:00Z",
 | 
			
		||||
  "issuanceDate": "2023-05-16T07:18:52Z",
 | 
			
		||||
  "name": "Simple assertion of achievement 1",
 | 
			
		||||
  "credentialSubject": {
 | 
			
		||||
    "id": "did:web:dc.1edtech.org:wellspring2022:wellspring-portal:learner:d4e1f6ad-9696-41cb-8729-8ff741f96c6a",
 | 
			
		||||
    "type": "AchievementSubject",
 | 
			
		||||
    "achievement": {
 | 
			
		||||
      "id": "http://dc.1edtech.org/wellspring2022/wellspring-portal/achievements/c44d8939-c237-4420-902c-9af305b15e2f",
 | 
			
		||||
      "type": "Achievement",
 | 
			
		||||
      "achievementType": "Achievement",
 | 
			
		||||
      "criteria": {
 | 
			
		||||
        "narrative": "The credential must pass verification."
 | 
			
		||||
      },
 | 
			
		||||
      "description": "This is a test achievement.",
 | 
			
		||||
      "name": "Achievement 1"
 | 
			
		||||
    },
 | 
			
		||||
    "source": {
 | 
			
		||||
      "id": "did:web:dc.1edtech.org:wellspring2022:wellspring-portal:org:7ad80b28-4f3f-414d-85ec-6c0684344e5c",
 | 
			
		||||
      "type": "Profile",
 | 
			
		||||
      "name": "Wellspring School"
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  "proof": {
 | 
			
		||||
    "type": "Ed25519Signature2020",
 | 
			
		||||
    "created": "2023-05-16T07:18:52Z",
 | 
			
		||||
    "proofPurpose": "assertionMethod",
 | 
			
		||||
    "verificationMethod": "did:web:dc.1edtech.org:wellspring2022:wellspring-portal:org:7ad80b28-4f3f-414d-85ec-6c0684344e5c#key-0",
 | 
			
		||||
    "proofValue": "zYQ6iszNf2qCqisuWvk1AkerTTp69RiofNWWzWp4s5TJwzBfFgieBSA5Knyjco6crJbPkJ1mvM1hzA2HLYfU8w8C"
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user