Fixed up a couple of steps in the did:web embedded proof probe

This commit is contained in:
Andy Miller (IMS) 2023-05-16 10:36:57 +09:00
parent 465ba7df55
commit 0f839cc0c2

View File

@ -24,6 +24,7 @@ import info.weboftrust.ldsignatures.LdProof;
import info.weboftrust.ldsignatures.verifier.Ed25519Signature2020LdVerifier; import info.weboftrust.ldsignatures.verifier.Ed25519Signature2020LdVerifier;
import jakarta.json.JsonArray; import jakarta.json.JsonArray;
import jakarta.json.JsonObject; import jakarta.json.JsonObject;
import jakarta.json.JsonString;
import jakarta.json.JsonStructure; import jakarta.json.JsonStructure;
import jakarta.json.JsonValue; import jakarta.json.JsonValue;
@ -45,15 +46,17 @@ public class EmbeddedProofProbe extends Probe<VerifiableCredential> {
@Override @Override
public ReportItems run(VerifiableCredential crd, RunContext ctx) throws Exception { 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) { if (proofs == null || proofs.size() == 0) {
return error("The verifiable credential is missing a proof.", ctx); return error("The verifiable credential is missing a proof.", ctx);
} }
// get proof of standard type and purpose // 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(); .findFirst();
if (!selectedProof.isPresent()) { if (!selectedProof.isPresent()) {
@ -81,7 +84,8 @@ public class EmbeddedProofProbe extends Probe<VerifiableCredential> {
// did:key:[publicKeyMultibase] // did:key:[publicKeyMultibase]
// did:web:[url-encoded domain-name][:path]* // did:web:[url-encoded domain-name][:path]*
// http/s://[location of a Ed25519VerificationKey2020 document] // 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 publicKeyMultibase;
String controller = null; String controller = null;
@ -98,22 +102,25 @@ public class EmbeddedProofProbe extends Probe<VerifiableCredential> {
if (method.getSchemeSpecificPart().startsWith("key:")) { if (method.getSchemeSpecificPart().startsWith("key:")) {
publicKeyMultibase = method.getSchemeSpecificPart().substring("key:".length()); publicKeyMultibase = method.getSchemeSpecificPart().substring("key:".length());
} else if (method.getSchemeSpecificPart().startsWith("web:")) { } 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. // 1. Replace ":" with "/" in the method specific identifier to obtain the fully
specificId = specificId.replaceAll(":", "/"); // qualified domain name and optional path.
methodSpecificId = methodSpecificId.replaceAll(":", "/");
// 2. If the domain contains a port percent decode the colon. // 2. If the domain contains a port percent decode the colon.
String portPercentEncoded = URLEncoder.encode(":", Charset.forName("UTF-8")); String portPercentEncoded = URLEncoder.encode(":", Charset.forName("UTF-8"));
int index = specificId.indexOf(portPercentEncoded); int index = methodSpecificId.indexOf(portPercentEncoded);
if (index >= 0 && index < specificId.indexOf("/")) { if (index >= 0 && index < methodSpecificId.indexOf("/")) {
specificId = specificId.replace(portPercentEncoded, ":"); methodSpecificId = methodSpecificId.replace(portPercentEncoded, ":");
} }
// 3. Generate an HTTPS URL to the expected location of the DID document by prepending https://. // 3. Generate an HTTPS URL to the expected location of the DID document by
URI uri = new URI("https://" + specificId); // prepending https://.
URI uri = new URI("https://" + methodSpecificId);
// 4. If no path has been specified in the URL, append /.well-known. // 4. If no path has been specified in the URL, append /.well-known.
if (uri.getPath() == null) { if (uri.getPath() == null) {
@ -121,30 +128,55 @@ public class EmbeddedProofProbe extends Probe<VerifiableCredential> {
} }
// 5. Append /did.json to complete the URL. // 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. // 6. Perform an HTTP GET request to the URL using an agent that can
// 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. // successfully negotiate a secure HTTPS connection, which enforces the security
Document keyDocument = credentiaHolder.getCredential().getDocumentLoader().loadDocument(uri, new DocumentLoaderOptions()); // requirements as described in 2.6 Security and privacy considerations.
Optional<JsonStructure> keyStructure = keyDocument.getJsonContent(); // 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()) { 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" // check did in "assertionMethod"
JsonArray assertionMethod = keyStructure.get().asJsonObject() JsonArray assertionMethod = keyStructure.get().asJsonObject()
.getJsonArray("assertionMethod"); .getJsonArray("assertionMethod");
if (assertionMethod != null && !assertionMethod.stream().anyMatch(n -> n.toString().equals(method.toString()))) { 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); return error("Assertion method " + method + " not found in DID document.", ctx);
} }
}
// get keys from "verificationMethod" // get keys from "verificationMethod"
JsonArray keyVerificationMethod = keyStructure.get().asJsonObject() JsonArray keyVerificationMethod = keyStructure.get().asJsonObject()
.getJsonArray("verificationMethod"); .getJsonArray("verificationMethod");
if (keyVerificationMethod == null) { 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<JsonValue> verificationMethodMaybe = keyVerificationMethod.stream().filter(n -> n.asJsonObject().getString("id").equals(method.toString())) Optional<JsonValue> verificationMethodMaybe = keyVerificationMethod.stream()
.filter(n -> n.asJsonObject().getString("id").equals(method.toString()))
.findFirst(); .findFirst();
if (verificationMethodMaybe.isEmpty()) { if (verificationMethodMaybe.isEmpty()) {
return error("Verification method " + method + " not found in DID document.", ctx); return error("Verification method " + method + " not found in DID document.", ctx);
@ -159,7 +191,8 @@ public class EmbeddedProofProbe extends Probe<VerifiableCredential> {
} }
} else if (method.getScheme().equals("http") || method.getScheme().equals("https")) { } else if (method.getScheme().equals("http") || method.getScheme().equals("https")) {
try { try {
Document keyDocument = credentiaHolder.getCredential().getDocumentLoader().loadDocument(method, new DocumentLoaderOptions()); Document keyDocument = credentialHolder.getCredential().getDocumentLoader().loadDocument(method,
new DocumentLoaderOptions());
Optional<JsonStructure> keyStructure = keyDocument.getJsonContent(); Optional<JsonStructure> keyStructure = keyDocument.getJsonContent();
if (keyStructure.isEmpty()) { if (keyStructure.isEmpty()) {
return error("Key document not found at " + method, ctx); return error("Key document not found at " + method, ctx);
@ -168,7 +201,8 @@ public class EmbeddedProofProbe extends Probe<VerifiableCredential> {
// First look for a Ed25519VerificationKey2020 document // First look for a Ed25519VerificationKey2020 document
controller = keyStructure.get().asJsonObject().getString("controller"); controller = keyStructure.get().asJsonObject().getString("controller");
if (StringUtils.isBlank(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 // that is a Ed25519VerificationKey2020 document
JsonObject keyVerificationMethod = keyStructure.get().asJsonObject() JsonObject keyVerificationMethod = keyStructure.get().asJsonObject()
.getJsonObject("verificationMethod"); .getJsonObject("verificationMethod");
@ -202,8 +236,9 @@ public class EmbeddedProofProbe extends Probe<VerifiableCredential> {
} }
if (controller != null) { if (controller != null) {
if (!controller.equals(credentiaHolder.getCredential().getIssuer().toString())) { if (!controller.equals(credentialHolder.getCredential().getIssuer().toString())) {
return error("Key controller does not match issuer: " + credentiaHolder.getCredential().getIssuer(), ctx); return error("Key controller does not match issuer: " + credentialHolder.getCredential().getIssuer(),
ctx);
} }
} }
@ -213,7 +248,7 @@ public class EmbeddedProofProbe extends Probe<VerifiableCredential> {
Ed25519Signature2020LdVerifier verifier = new Ed25519Signature2020LdVerifier(publicKey); Ed25519Signature2020LdVerifier verifier = new Ed25519Signature2020LdVerifier(publicKey);
try { try {
boolean verify = verifier.verify(credentiaHolder.getCredential(), proof); boolean verify = verifier.verify(credentialHolder.getCredential(), proof);
if (!verify) { if (!verify) {
return error("Embedded proof verification failed.", ctx); return error("Embedded proof verification failed.", ctx);
} }