From f499ef32e9812e78f4ffd557d5bcebeb40fea047 Mon Sep 17 00:00:00 2001 From: Xavi Aracil Date: Wed, 22 Mar 2023 10:49:42 +0100 Subject: [PATCH 1/6] Added did:web verification method --- .../inspect/vc/probe/EmbeddedProofProbe.java | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) 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 4a90cec..a51184c 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 @@ -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,10 @@ 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.JsonStructure; +import jakarta.json.JsonValue; /** * A Probe that verifies a credential's embedded proof. @@ -75,6 +79,7 @@ public class EmbeddedProofProbe extends Probe { // // [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] @@ -92,6 +97,63 @@ public class EmbeddedProofProbe extends Probe { } else if (method.getScheme().equals("did")) { if (method.getSchemeSpecificPart().startsWith("key:")) { publicKeyMultibase = method.getSchemeSpecificPart().substring("key:".length()); + } else if (method.getSchemeSpecificPart().startsWith("web:")) { + String specificId = method.getSchemeSpecificPart().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. + specificId = specificId.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, ":"); + } + + // 3. Generate an HTTPS URL to the expected location of the DID document by prepending https://. + URI uri = new URI("https://" + specificId); + + // 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("/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(); + if (keyStructure.isEmpty()) { + return error("Key document not found at " + method + ". Uri " + uri + " doesn't return a valid document", 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); + } + + // 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 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); } From d90bb7245a9e3126055cd79c22f04da2720d39d8 Mon Sep 17 00:00:00 2001 From: "Andy Miller (IMS)" <48326098+amiller-ims@users.noreply.github.com> Date: Tue, 16 May 2023 10:35:07 +0900 Subject: [PATCH 2/6] Add a did:web method test --- .../java/org/oneedtech/inspect/vc/OB30Tests.java | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/inspector-vc/src/test/java/org/oneedtech/inspect/vc/OB30Tests.java b/inspector-vc/src/test/java/org/oneedtech/inspect/vc/OB30Tests.java index 2367fea..1ad10dc 100644 --- a/inspector-vc/src/test/java/org/oneedtech/inspect/vc/OB30Tests.java +++ b/inspector-vc/src/test/java/org/oneedtech/inspect/vc/OB30Tests.java @@ -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); }); From 465ba7df55305f82bdee74c2922430ce4dda12bf Mon Sep 17 00:00:00 2001 From: "Andy Miller (IMS)" <48326098+amiller-ims@users.noreply.github.com> Date: Tue, 16 May 2023 10:35:56 +0900 Subject: [PATCH 3/6] Renamed did:key method resource --- .../resources/ob30/simple-did-method.json | 33 ----- .../resources/ob30/simple-did-web-method.json | 134 ++++++++++++++++++ 2 files changed, 134 insertions(+), 33 deletions(-) delete mode 100644 inspector-vc/src/test/resources/ob30/simple-did-method.json create mode 100644 inspector-vc/src/test/resources/ob30/simple-did-web-method.json diff --git a/inspector-vc/src/test/resources/ob30/simple-did-method.json b/inspector-vc/src/test/resources/ob30/simple-did-method.json deleted file mode 100644 index 764298f..0000000 --- a/inspector-vc/src/test/resources/ob30/simple-did-method.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "@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" : "urn:uuid:280c19b6-9680-4a37-ba84-e38b1a4e4584", - "type" : [ "VerifiableCredential", "AchievementCredential" ], - "issuer" : { - "type" : [ "Profile" ], - "name" : "Andy F. Miller", - "id" : "urn:uuid:6f2e33e5-7a29-4155-840a-59483ba10164" - }, - "issuanceDate" : "2022-11-10T07:38:00-08:00", - "name" : "test 1", - "credentialSubject" : { - "id" : "urn:uuid:6f2e33e5-7a29-4155-840a-59483ba10164", - "type" : [ "AchievementSubject" ], - "achievement" : { - "id" : "urn:uuid:35258e6f-4c05-4215-8ada-38a5a5b80510", - "type" : [ "Achievement" ], - "achievementType" : "Achievement", - "name" : "test 1", - "description" : "This is a test achievement", - "criteria" : { - "narrative" : "There is no criteria" - } - } - }, - "proof" : [ { - "type" : "Ed25519Signature2020", - "created" : "2022-11-16T20:39:53Z", - "proofPurpose" : "assertionMethod", - "verificationMethod" : "did:key:z6MkwAQmEfso8UjHJZTQajRtqR5hDxAD95iJD4z53XnKCFms", - "proofValue" : "z361ueyGzREPvsWdnWUfkzTKXEd6u2DPPu2kDw3pDERJmzDFCqsuaPneqcRgz2hk9ycaNDYmC4Fy9c6S6BDDt5fVB" - } ] -} diff --git a/inspector-vc/src/test/resources/ob30/simple-did-web-method.json b/inspector-vc/src/test/resources/ob30/simple-did-web-method.json new file mode 100644 index 0000000..57c8b7c --- /dev/null +++ b/inspector-vc/src/test/resources/ob30/simple-did-web-method.json @@ -0,0 +1,134 @@ +{ + "@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://localhost:8082/credentials/a9821486-26fc-4a89-924b-ed2521395003", + "type": [ + "VerifiableCredential", + "AchievementCredential" + ], + "issuer": { + "id": "did:web:localhost%3A8082:profiles:1bb90bff-8fda-49eb-884c-0fa9d209d178", + "type": "Profile", + "name": "Wellspring School" + }, + "awardedDate": "2023-05-02T00:33:00Z", + "issuanceDate": "2023-05-15T23:56:14Z", + "expirationDate": "2024-05-02T02:47:00Z", + "name": "Achievement 2", + "description": "This is a short description.", + "evidence": [ + { + "type": "Evidence", + "audience": "General", + "description": "My photos", + "genre": "Photography", + "name": "Photography portfolio", + "narrative": "This is __all__ of my photos" + } + ], + "credentialSubject": { + "id": "did:web:localhost%3A8082:profiles:abb48d60-5619-4b6f-9f3f-699b7c4b49cd", + "type": "AchievementSubject", + "achievement": { + "id": "http://localhost:8082/achievements/5e5f7f83-cf05-49d1-81a7-d9cb6b0ffece", + "type": "Achievement", + "achievementType": "Course", + "alignment": [ + { + "type": "Alignment", + "targetFramework": "Construction Accounting", + "targetName": "Preparing cost estimates and bids", + "targetType": "CFItem", + "targetUrl": "https://casenetwork.imsglobal.org/uri/c865f9de-aea3-11eb-86a8-0242ac140003", + "targetFrameworkIdentifier": "bdf9b670-aea3-11eb-85bc-0242ac140003", + "targetIdentifier": "c865f9de-aea3-11eb-86a8-0242ac140003" + } + ], + "creditsAvailable": 3, + "criteria": { + "narrative": "This is the criteria:\r\n1. First criteria\r\n1. Second criteria" + }, + "description": "This is a short description", + "fieldOfStudy": "Biology", + "humanCode": "B-101", + "@language": "en", + "name": "Achievement 2", + "resultDescription": [ + { + "id": "25838e15-81af-4582-97a9-932e07a8f3ce", + "type": "ResultDescription", + "allowedValue": [ + "A", + "B", + "C", + "D", + "F" + ], + "name": "Grade", + "requiredLevel": "431eff1b-0981-4656-9b0c-8a3957f481e7", + "requiredValue": "C", + "resultType": "LetterGrade", + "rubricCriterionLevel": [ + { + "id": "a6204c1f-f2e9-4cd7-b06b-727ca7564778", + "type": "RubricCriterionLevel", + "description": "Basic level", + "level": "Basic", + "name": "Basic", + "points": "1" + }, + { + "id": "431eff1b-0981-4656-9b0c-8a3957f481e7", + "type": "RubricCriterionLevel", + "description": "Advanced level", + "level": "Advanced", + "name": "Advanced", + "points": "2" + } + ], + "valueMax": "A", + "valueMin": "C" + } + ], + "specialization": "Nanotechnology", + "tag": [ + "this", + " that", + " the other" + ], + "version": "1" + }, + "activityEndDate": "2023-03-31T02:47:00Z", + "activityStartDate": "2023-01-01T02:47:00Z", + "creditsEarned": 3, + "licenseNumber": "1234567890", + "narrative": "This is a **longer** description.", + "result": [ + { + "type": "Result", + "achievedLevel": "a6204c1f-f2e9-4cd7-b06b-727ca7564778", + "resultDescription": "25838e15-81af-4582-97a9-932e07a8f3ce", + "status": "Completed", + "value": "A" + } + ], + "role": "Arbitrator", + "source": { + "id": "did:web:localhost%3A8082:profiles:1bb90bff-8fda-49eb-884c-0fa9d209d178", + "type": "Profile", + "name": "Wellspring School" + }, + "term": "Winter" + }, + "proof": { + "type": "Ed25519Signature2020", + "created": "2023-05-15T23:56:14Z", + "proofPurpose": "assertionMethod", + "verificationMethod": "did:web:localhost%3A8082:profiles:1bb90bff-8fda-49eb-884c-0fa9d209d178#key-0", + "proofValue": "zpwCWNLeW5GUaJCzNxUPeLB2S7eXyukudaqKbVDUcZtQUJpTtXu2evP73Siv63V6FYipcP8WrVYovgZ1HrsKHKZZ" + } +} \ No newline at end of file From 0f839cc0c2d7270d20b3f1d20304f83829642807 Mon Sep 17 00:00:00 2001 From: "Andy Miller (IMS)" <48326098+amiller-ims@users.noreply.github.com> Date: Tue, 16 May 2023 10:36:57 +0900 Subject: [PATCH 4/6] Fixed up a couple of steps in the did:web embedded proof probe --- .../inspect/vc/probe/EmbeddedProofProbe.java | 101 ++++++++++++------ 1 file changed, 68 insertions(+), 33 deletions(-) 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); } From 149af3ea5c47caf94dc698d00515e5a7223197c6 Mon Sep 17 00:00:00 2001 From: "Andy Miller (IMS)" <48326098+amiller-ims@users.noreply.github.com> Date: Tue, 16 May 2023 12:11:01 +0900 Subject: [PATCH 5/6] Add a simple did:web method sample Depends on wellspring 2022 portal app --- .../org/oneedtech/inspect/vc/Samples.java | 3 +- .../resources/ob30/simple-did-key-method.json | 33 +++++ .../resources/ob30/simple-did-web-method.json | 119 +++--------------- 3 files changed, 51 insertions(+), 104 deletions(-) create mode 100644 inspector-vc/src/test/resources/ob30/simple-did-key-method.json diff --git a/inspector-vc/src/test/java/org/oneedtech/inspect/vc/Samples.java b/inspector-vc/src/test/java/org/oneedtech/inspect/vc/Samples.java index ab74a9e..b0f2607 100644 --- a/inspector-vc/src/test/java/org/oneedtech/inspect/vc/Samples.java +++ b/inspector-vc/src/test/java/org/oneedtech/inspect/vc/Samples.java @@ -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); diff --git a/inspector-vc/src/test/resources/ob30/simple-did-key-method.json b/inspector-vc/src/test/resources/ob30/simple-did-key-method.json new file mode 100644 index 0000000..764298f --- /dev/null +++ b/inspector-vc/src/test/resources/ob30/simple-did-key-method.json @@ -0,0 +1,33 @@ +{ + "@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" : "urn:uuid:280c19b6-9680-4a37-ba84-e38b1a4e4584", + "type" : [ "VerifiableCredential", "AchievementCredential" ], + "issuer" : { + "type" : [ "Profile" ], + "name" : "Andy F. Miller", + "id" : "urn:uuid:6f2e33e5-7a29-4155-840a-59483ba10164" + }, + "issuanceDate" : "2022-11-10T07:38:00-08:00", + "name" : "test 1", + "credentialSubject" : { + "id" : "urn:uuid:6f2e33e5-7a29-4155-840a-59483ba10164", + "type" : [ "AchievementSubject" ], + "achievement" : { + "id" : "urn:uuid:35258e6f-4c05-4215-8ada-38a5a5b80510", + "type" : [ "Achievement" ], + "achievementType" : "Achievement", + "name" : "test 1", + "description" : "This is a test achievement", + "criteria" : { + "narrative" : "There is no criteria" + } + } + }, + "proof" : [ { + "type" : "Ed25519Signature2020", + "created" : "2022-11-16T20:39:53Z", + "proofPurpose" : "assertionMethod", + "verificationMethod" : "did:key:z6MkwAQmEfso8UjHJZTQajRtqR5hDxAD95iJD4z53XnKCFms", + "proofValue" : "z361ueyGzREPvsWdnWUfkzTKXEd6u2DPPu2kDw3pDERJmzDFCqsuaPneqcRgz2hk9ycaNDYmC4Fy9c6S6BDDt5fVB" + } ] +} diff --git a/inspector-vc/src/test/resources/ob30/simple-did-web-method.json b/inspector-vc/src/test/resources/ob30/simple-did-web-method.json index 57c8b7c..8b6c156 100644 --- a/inspector-vc/src/test/resources/ob30/simple-did-web-method.json +++ b/inspector-vc/src/test/resources/ob30/simple-did-web-method.json @@ -5,130 +5,43 @@ "https://purl.imsglobal.org/spec/ob/v3p0/extensions.json", "https://w3id.org/security/suites/ed25519-2020/v1" ], - "id": "http://localhost:8082/credentials/a9821486-26fc-4a89-924b-ed2521395003", + "id": "http://dc.1edtech.org/wellspring2022/wellspring-portal/credentials/5d7b7ff6-b1d5-47d5-83df-faf22533ba8f", "type": [ "VerifiableCredential", "AchievementCredential" ], "issuer": { - "id": "did:web:localhost%3A8082:profiles:1bb90bff-8fda-49eb-884c-0fa9d209d178", + "id": "did:web:dc.1edtech.org:wellspring2022:wellspring-portal:profiles:7ad80b28-4f3f-414d-85ec-6c0684344e5c", "type": "Profile", "name": "Wellspring School" }, - "awardedDate": "2023-05-02T00:33:00Z", - "issuanceDate": "2023-05-15T23:56:14Z", - "expirationDate": "2024-05-02T02:47:00Z", - "name": "Achievement 2", - "description": "This is a short description.", - "evidence": [ - { - "type": "Evidence", - "audience": "General", - "description": "My photos", - "genre": "Photography", - "name": "Photography portfolio", - "narrative": "This is __all__ of my photos" - } - ], + "awardedDate": "2023-05-16T11:27:00Z", + "issuanceDate": "2023-05-16T03:06:16Z", + "name": "Simple assertion of achievement 1", "credentialSubject": { - "id": "did:web:localhost%3A8082:profiles:abb48d60-5619-4b6f-9f3f-699b7c4b49cd", + "id": "did:web:dc.1edtech.org:wellspring2022:wellspring-portal:profiles:d4e1f6ad-9696-41cb-8729-8ff741f96c6a", "type": "AchievementSubject", "achievement": { - "id": "http://localhost:8082/achievements/5e5f7f83-cf05-49d1-81a7-d9cb6b0ffece", + "id": "http://dc.1edtech.org/wellspring2022/wellspring-portal/achievements/c44d8939-c237-4420-902c-9af305b15e2f", "type": "Achievement", - "achievementType": "Course", - "alignment": [ - { - "type": "Alignment", - "targetFramework": "Construction Accounting", - "targetName": "Preparing cost estimates and bids", - "targetType": "CFItem", - "targetUrl": "https://casenetwork.imsglobal.org/uri/c865f9de-aea3-11eb-86a8-0242ac140003", - "targetFrameworkIdentifier": "bdf9b670-aea3-11eb-85bc-0242ac140003", - "targetIdentifier": "c865f9de-aea3-11eb-86a8-0242ac140003" - } - ], - "creditsAvailable": 3, + "achievementType": "Achievement", "criteria": { - "narrative": "This is the criteria:\r\n1. First criteria\r\n1. Second criteria" + "narrative": "The credential must pass verification." }, - "description": "This is a short description", - "fieldOfStudy": "Biology", - "humanCode": "B-101", - "@language": "en", - "name": "Achievement 2", - "resultDescription": [ - { - "id": "25838e15-81af-4582-97a9-932e07a8f3ce", - "type": "ResultDescription", - "allowedValue": [ - "A", - "B", - "C", - "D", - "F" - ], - "name": "Grade", - "requiredLevel": "431eff1b-0981-4656-9b0c-8a3957f481e7", - "requiredValue": "C", - "resultType": "LetterGrade", - "rubricCriterionLevel": [ - { - "id": "a6204c1f-f2e9-4cd7-b06b-727ca7564778", - "type": "RubricCriterionLevel", - "description": "Basic level", - "level": "Basic", - "name": "Basic", - "points": "1" - }, - { - "id": "431eff1b-0981-4656-9b0c-8a3957f481e7", - "type": "RubricCriterionLevel", - "description": "Advanced level", - "level": "Advanced", - "name": "Advanced", - "points": "2" - } - ], - "valueMax": "A", - "valueMin": "C" - } - ], - "specialization": "Nanotechnology", - "tag": [ - "this", - " that", - " the other" - ], - "version": "1" + "description": "This is a test achievement.", + "name": "Achievement 1" }, - "activityEndDate": "2023-03-31T02:47:00Z", - "activityStartDate": "2023-01-01T02:47:00Z", - "creditsEarned": 3, - "licenseNumber": "1234567890", - "narrative": "This is a **longer** description.", - "result": [ - { - "type": "Result", - "achievedLevel": "a6204c1f-f2e9-4cd7-b06b-727ca7564778", - "resultDescription": "25838e15-81af-4582-97a9-932e07a8f3ce", - "status": "Completed", - "value": "A" - } - ], - "role": "Arbitrator", "source": { - "id": "did:web:localhost%3A8082:profiles:1bb90bff-8fda-49eb-884c-0fa9d209d178", + "id": "did:web:dc.1edtech.org:wellspring2022:wellspring-portal:profiles:7ad80b28-4f3f-414d-85ec-6c0684344e5c", "type": "Profile", "name": "Wellspring School" - }, - "term": "Winter" + } }, "proof": { "type": "Ed25519Signature2020", - "created": "2023-05-15T23:56:14Z", + "created": "2023-05-16T03:06:16Z", "proofPurpose": "assertionMethod", - "verificationMethod": "did:web:localhost%3A8082:profiles:1bb90bff-8fda-49eb-884c-0fa9d209d178#key-0", - "proofValue": "zpwCWNLeW5GUaJCzNxUPeLB2S7eXyukudaqKbVDUcZtQUJpTtXu2evP73Siv63V6FYipcP8WrVYovgZ1HrsKHKZZ" + "verificationMethod": "did:web:dc.1edtech.org:wellspring2022:wellspring-portal:profiles:7ad80b28-4f3f-414d-85ec-6c0684344e5c#key-0", + "proofValue": "z3hxUKSkeLNyk9dj9RuY8K6Zp7SjdQz6C3ueM9KiD24ex5oAQ1K4txZ92jXybLYwp42fzMtR4fNqvFuev6NkQJKyP" } } \ No newline at end of file From 981398f418b2cd695b27b14a7d11c94cf8378d94 Mon Sep 17 00:00:00 2001 From: "Andy Miller (IMS)" <48326098+amiller-ims@users.noreply.github.com> Date: Tue, 16 May 2023 16:27:57 +0900 Subject: [PATCH 6/6] Switch to using org and learner paths for did-web url --- .../test/resources/ob30/simple-did-web-method.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/inspector-vc/src/test/resources/ob30/simple-did-web-method.json b/inspector-vc/src/test/resources/ob30/simple-did-web-method.json index 8b6c156..718e0a2 100644 --- a/inspector-vc/src/test/resources/ob30/simple-did-web-method.json +++ b/inspector-vc/src/test/resources/ob30/simple-did-web-method.json @@ -11,15 +11,15 @@ "AchievementCredential" ], "issuer": { - "id": "did:web:dc.1edtech.org:wellspring2022:wellspring-portal:profiles:7ad80b28-4f3f-414d-85ec-6c0684344e5c", + "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-16T03:06:16Z", + "issuanceDate": "2023-05-16T07:18:52Z", "name": "Simple assertion of achievement 1", "credentialSubject": { - "id": "did:web:dc.1edtech.org:wellspring2022:wellspring-portal:profiles:d4e1f6ad-9696-41cb-8729-8ff741f96c6a", + "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", @@ -32,16 +32,16 @@ "name": "Achievement 1" }, "source": { - "id": "did:web:dc.1edtech.org:wellspring2022:wellspring-portal:profiles:7ad80b28-4f3f-414d-85ec-6c0684344e5c", + "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-16T03:06:16Z", + "created": "2023-05-16T07:18:52Z", "proofPurpose": "assertionMethod", - "verificationMethod": "did:web:dc.1edtech.org:wellspring2022:wellspring-portal:profiles:7ad80b28-4f3f-414d-85ec-6c0684344e5c#key-0", - "proofValue": "z3hxUKSkeLNyk9dj9RuY8K6Zp7SjdQz6C3ueM9KiD24ex5oAQ1K4txZ92jXybLYwp42fzMtR4fNqvFuev6NkQJKyP" + "verificationMethod": "did:web:dc.1edtech.org:wellspring2022:wellspring-portal:org:7ad80b28-4f3f-414d-85ec-6c0684344e5c#key-0", + "proofValue": "zYQ6iszNf2qCqisuWvk1AkerTTp69RiofNWWzWp4s5TJwzBfFgieBSA5Knyjco6crJbPkJ1mvM1hzA2HLYfU8w8C" } } \ No newline at end of file