diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/EmbeddedProofProbe.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/EmbeddedProofProbe.java index a51184c..5d402f1 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/EmbeddedProofProbe.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/EmbeddedProofProbe.java @@ -24,6 +24,7 @@ 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; @@ -45,16 +46,18 @@ public class EmbeddedProofProbe extends Probe { @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 proofs = credentiaHolder.getProofs(); + List 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 selectedProof = proofs.stream().filter(proof -> proof.isType("Ed25519Signature2020") && proof.getProofPurpose().equals("assertionMethod")) - .findFirst(); + Optional selectedProof = proofs.stream().filter( + proof -> proof.isType("Ed25519Signature2020") && proof.getProofPurpose().equals("assertionMethod")) + .findFirst(); if (!selectedProof.isPresent()) { return error("No proof with type \"Ed25519Signature2020\" or proof purpose \"assertionMethod\" found", ctx); @@ -74,14 +77,15 @@ public class EmbeddedProofProbe extends Probe { // The verification method must dereference to an Ed25519VerificationKey2020. // Danubetech's Ed25519Signature2020LdVerifier expects the decoded public key // from the Ed25519VerificationKey2020 (32 bytes). - // + // // Formats accepted: // // [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; @@ -98,22 +102,25 @@ public class EmbeddedProofProbe extends Probe { if (method.getSchemeSpecificPart().startsWith("key:")) { publicKeyMultibase = method.getSchemeSpecificPart().substring("key:".length()); } else if (method.getSchemeSpecificPart().startsWith("web:")) { - String specificId = method.getSchemeSpecificPart().substring("web:".length()); + String methodSpecificId = method.getRawSchemeSpecificPart().substring("web:".length()); - // read algorithm at https://w3c-ccg.github.io/did-method-web/#read-resolve. Steps in comments + // 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. - specificId = specificId.replaceAll(":", "/"); + // 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 = specificId.indexOf(portPercentEncoded); - if (index >= 0 && index < specificId.indexOf("/")) { - specificId = specificId.replace(portPercentEncoded, ":"); + 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://" + specificId); + // 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) { @@ -121,31 +128,56 @@ public class EmbeddedProofProbe extends Probe { } // 5. Append /did.json to complete the URL. - uri = uri.resolve("/did.json"); + 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. - Document keyDocument = credentiaHolder.getCredential().getDocumentLoader().loadDocument(uri, new DocumentLoaderOptions()); - Optional keyStructure = keyDocument.getJsonContent(); + // 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 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", ctx); + 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 && !assertionMethod.stream().anyMatch(n -> n.toString().equals(method.toString()))) { - return error("Assertion method " + method + " not found in DID document.", ctx); + .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"); + .getJsonArray("verificationMethod"); if (keyVerificationMethod == null) { - return error("Document doesn't have a list of verification methods at uri " + uri, ctx); + return error("Document doesn't have a list of verification methods at URI: " + uri, ctx); } - Optional verificationMethodMaybe = keyVerificationMethod.stream().filter(n -> n.asJsonObject().getString("id").equals(method.toString())) - .findFirst(); + Optional 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); } @@ -159,7 +191,8 @@ public class EmbeddedProofProbe extends Probe { } } 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 keyStructure = keyDocument.getJsonContent(); if (keyStructure.isEmpty()) { return error("Key document not found at " + method, ctx); @@ -168,7 +201,8 @@ public class EmbeddedProofProbe extends Probe { // 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"); @@ -202,8 +236,9 @@ public class EmbeddedProofProbe extends Probe { } 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); } } @@ -213,7 +248,7 @@ public class EmbeddedProofProbe extends Probe { 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); }