From 78c1cbe279fd528570c3f7fff9fc0c0ab376fb8d Mon Sep 17 00:00:00 2001 From: "Andy Miller (IMS)" <48326098+amiller-ims@users.noreply.github.com> Date: Wed, 16 Nov 2022 12:29:22 -0800 Subject: [PATCH 1/2] Add several more ob3 tests --- .../inspect/vc/probe/EmbeddedProofProbe.java | 78 +++++++++++-------- .../org/oneedtech/inspect/vc/OB30Tests.java | 51 +++++++++++- .../org/oneedtech/inspect/vc/Samples.java | 4 + .../resources/ob30/simple-did-method.json | 47 +++++++++++ .../simple-err-proof-method-no-scheme.json | 47 +++++++++++ ...e-err-proof-method-unknown-did-method.json | 47 +++++++++++ ...imple-err-proof-method-unknown-scheme.json | 47 +++++++++++ 7 files changed, 287 insertions(+), 34 deletions(-) create mode 100644 inspector-vc/src/test/resources/ob30/simple-did-method.json create mode 100644 inspector-vc/src/test/resources/ob30/simple-err-proof-method-no-scheme.json create mode 100644 inspector-vc/src/test/resources/ob30/simple-err-proof-method-unknown-did-method.json create mode 100644 inspector-vc/src/test/resources/ob30/simple-err-proof-method-unknown-scheme.json 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 8baeb9e..b3fe3b4 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 @@ -65,57 +65,60 @@ 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). - - String publicKeyMultibase; - String controller = null; - + // // Formats accepted: // // [controller]#[publicKeyMultibase] // did:key:[publicKeyMultibase] // http/s://[location of a Ed25519VerificationKey2020 document] // http/s://[location of a controller document with a 'verificationMethod' with a Ed25519VerificationKey2020] - // [publicKeyMultibase] + + String publicKeyMultibase; + String controller = null; publicKeyMultibase = method.toString(); - if (method.getFragment() != null) { + if (method.getFragment() != null && IsValidPublicKeyMultibase(method.getFragment())) { publicKeyMultibase = method.getFragment(); controller = method.toString().substring(0, method.toString().indexOf("#")); } else { - if (method.getScheme().equals("did")) { + if (StringUtils.isBlank(method.getScheme())) { + return error("The verification method must be a valid URI (missing scheme)", ctx); + } else if (method.getScheme().equals("did")) { if (method.getSchemeSpecificPart().startsWith("key:")) { - publicKeyMultibase = method.getSchemeSpecificPart().substring(4); + publicKeyMultibase = method.getSchemeSpecificPart().substring("key:".length()); } else { return error("Unknown verification method: " + method, ctx); } } else if (method.getScheme().equals("http") || method.getScheme().equals("https")) { - // TODO: Can we use proof.getDocumentLoader()? - ConfigurableDocumentLoader keyDocumentLoader = new ConfigurableDocumentLoader(); - keyDocumentLoader.setEnableHttp(true); - keyDocumentLoader.setEnableHttps(true); - - Document keyDocument = keyDocumentLoader.loadDocument(method, new DocumentLoaderOptions()); - Optional keyStructure = keyDocument.getJsonContent(); - if (keyStructure.isEmpty()) { - return error("Key document not found at " + method, ctx); - } - - // 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' - // that is a Ed25519VerificationKey2020 document - JsonObject keyVerificationMethod = keyStructure.get().asJsonObject() - .getJsonObject("verificationMethod"); - if (keyVerificationMethod.isEmpty()) { - return error("Cannot parse key document from " + method, ctx); + try { + Document keyDocument = vc.getDocumentLoader().loadDocument(method, new DocumentLoaderOptions()); + Optional keyStructure = keyDocument.getJsonContent(); + if (keyStructure.isEmpty()) { + return error("Key document not found at " + method, ctx); } - controller = keyVerificationMethod.getString("controller"); - publicKeyMultibase = keyVerificationMethod.getString("publicKeyMultibase"); - } else { - publicKeyMultibase = keyStructure.get().asJsonObject().getString("publicKeyMultibase"); + + // 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' + // that is a Ed25519VerificationKey2020 document + JsonObject keyVerificationMethod = keyStructure.get().asJsonObject() + .getJsonObject("verificationMethod"); + if (keyVerificationMethod.isEmpty()) { + return error("Cannot parse key document from " + method, ctx); + } + controller = keyVerificationMethod.getString("controller"); + publicKeyMultibase = keyVerificationMethod.getString("publicKeyMultibase"); + } else { + publicKeyMultibase = keyStructure.get().asJsonObject().getString("publicKeyMultibase"); + } + + } catch (Exception e) { + return error("Invalid verification key URL: " + e.getMessage(), ctx); } + } else { + return error("Unknown verification method scheme: " + method.getScheme(), ctx); } } @@ -154,5 +157,16 @@ public class EmbeddedProofProbe extends Probe { return success(ctx); } + private Boolean IsValidPublicKeyMultibase(String publicKeyMultibase) { + try { + byte[] publicKeyMulticodec = Multibase.decode(publicKeyMultibase); + byte[] publicKey = Multicodec.decode(Codec.Ed25519PublicKey, publicKeyMulticodec); + return publicKey.length == 32; + } catch (Exception e) { + return false; + } + + } + public static final String ID = EmbeddedProofProbe.class.getSimpleName(); } 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 2ebea23..7778f6f 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 @@ -21,7 +21,7 @@ import com.google.common.collect.Iterables; public class OB30Tests { private static OB30Inspector validator; - private static boolean verbose = false; + private static boolean verbose = true; @BeforeAll static void setup() { @@ -40,6 +40,16 @@ public class OB30Tests { }); } + @Test + @Disabled("Do not have a valid sample") + void testSimpleDidMethodJsonValid() { + assertDoesNotThrow(()->{ + Report report = validator.run(Samples.OB30.JSON.SIMPLE_DID_METHOD_JSON.asFileResource()); + if(verbose) PrintHelper.print(report, true); + assertValid(report); + }); + } + @Test void testSimplePNGPlainValid() { assertDoesNotThrow(()->{ @@ -90,7 +100,8 @@ public class OB30Tests { @Test void testSimpleJsonInvalidProofMethod() { - //add some garbage chars to proofValue + // add some garbage chars to the verification method fragment + // it will be treated a URL to a verification key, but the URL will not be found assertDoesNotThrow(()->{ Report report = validator.run(Samples.OB30.JSON.SIMPLE_JSON_PROOF_METHOD_ERROR.asFileResource()); if(verbose) PrintHelper.print(report, true); @@ -100,6 +111,42 @@ public class OB30Tests { }); } + @Test + void testSimpleJsonInvalidProofMethodNoScheme() { + // The verificationMethod is not a URI (no scheme) + assertDoesNotThrow(()->{ + Report report = validator.run(Samples.OB30.JSON.SIMPLE_JSON_PROOF_METHOD_NO_SCHEME_ERROR.asFileResource()); + if(verbose) PrintHelper.print(report, true); + assertInvalid(report); + assertErrorCount(report, 1); + assertHasProbeID(report, EmbeddedProofProbe.ID, true); + }); + } + + @Test + void testSimpleJsonInvalidProofMethodUnknownScheme() { + // The verificationMethod is not a URI (no scheme) + assertDoesNotThrow(()->{ + Report report = validator.run(Samples.OB30.JSON.SIMPLE_JSON_PROOF_METHOD_UNKNOWN_SCHEME_ERROR.asFileResource()); + if(verbose) PrintHelper.print(report, true); + assertInvalid(report); + assertErrorCount(report, 1); + assertHasProbeID(report, EmbeddedProofProbe.ID, true); + }); + } + + @Test + void testSimpleJsonInvalidProofMethodUnknownDidMethod() { + // The verificationMethod is an unknown DID Method + assertDoesNotThrow(()->{ + Report report = validator.run(Samples.OB30.JSON.SIMPLE_JSON_PROOF_METHOD_UNKNOWN_DID_METHOD_ERROR.asFileResource()); + if(verbose) PrintHelper.print(report, true); + assertInvalid(report); + assertErrorCount(report, 1); + assertHasProbeID(report, EmbeddedProofProbe.ID, true); + }); + } + @Test void testSimpleJsonInvalidProofValue() { //add some garbage chars to proofValue 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 3c10f32..f47703a 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,9 +11,13 @@ 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_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); public final static Sample SIMPLE_JSON_PROOF_METHOD_ERROR = new Sample("ob30/simple-err-proof-method.json", false); + public final static Sample SIMPLE_JSON_PROOF_METHOD_NO_SCHEME_ERROR = new Sample("ob30/simple-err-proof-method-no-scheme.json", false); + public final static Sample SIMPLE_JSON_PROOF_METHOD_UNKNOWN_SCHEME_ERROR = new Sample("ob30/simple-err-proof-method-unknown-scheme.json", false); + public final static Sample SIMPLE_JSON_PROOF_METHOD_UNKNOWN_DID_METHOD_ERROR = new Sample("ob30/simple-err-proof-method-unknown-did-method.json", false); public final static Sample SIMPLE_JSON_PROOF_VALUE_ERROR = new Sample("ob30/simple-err-proof-value.json", false); public final static Sample SIMPLE_JSON_EXPIRED = new Sample("ob30/simple-err-expired.json", false); public final static Sample SIMPLE_JSON_ISSUED = new Sample("ob30/simple-err-issued.json", false); diff --git a/inspector-vc/src/test/resources/ob30/simple-did-method.json b/inspector-vc/src/test/resources/ob30/simple-did-method.json new file mode 100644 index 0000000..84fb747 --- /dev/null +++ b/inspector-vc/src/test/resources/ob30/simple-did-method.json @@ -0,0 +1,47 @@ +{ + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://purl.imsglobal.org/spec/ob/v3p0/context.json", + "https://w3id.org/security/suites/ed25519-2020/v1" + ], + "id": "http://example.com/credentials/3527", + "type": [ + "VerifiableCredential", + "OpenBadgeCredential" + ], + "issuer": { + "id": "https://example.com/issuers/876543", + "type": [ + "Profile" + ], + "name": "Example Corp" + }, + "issuanceDate": "2010-01-01T00:00:00Z", + "name": "Teamwork Badge", + "credentialSubject": { + "id": "did:example:ebfeb1f712ebc6f1c276e12ec21", + "type": [ + "AchievementSubject" + ], + "achievement": { + "id": "https://example.com/achievements/21st-century-skills/teamwork", + "type": [ + "Achievement" + ], + "criteria": { + "narrative": "Team members are nominated for this badge by their peers and recognized upon review by Example Corp management." + }, + "description": "This badge recognizes the development of the capacity to collaborate within a group environment.", + "name": "Teamwork" + } + }, + "proof": [ + { + "type": "Ed25519Signature2020", + "created": "2022-11-16T18:54:22Z", + "verificationMethod": "did:key:z6MknNHHrBzPytzu6CUBP9Lg7fg4KSBjzimc2Frh693YbMiv", + "proofPurpose": "assertionMethod", + "proofValue": "z5gJZKchSJEYPGeq6bsqiLKuxT6mXqAovPbqYX66CB7u9CSNFdV41vHtysjHFiitvoyhfPxsaZnWftrZZZW2txPQK" + } + ] +} \ No newline at end of file diff --git a/inspector-vc/src/test/resources/ob30/simple-err-proof-method-no-scheme.json b/inspector-vc/src/test/resources/ob30/simple-err-proof-method-no-scheme.json new file mode 100644 index 0000000..1fc7e2f --- /dev/null +++ b/inspector-vc/src/test/resources/ob30/simple-err-proof-method-no-scheme.json @@ -0,0 +1,47 @@ +{ + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://purl.imsglobal.org/spec/ob/v3p0/context.json", + "https://w3id.org/security/suites/ed25519-2020/v1" + ], + "id": "http://example.com/credentials/3527", + "type": [ + "VerifiableCredential", + "OpenBadgeCredential" + ], + "issuer": { + "id": "https://example.com/issuers/876543", + "type": [ + "Profile" + ], + "name": "Example Corp" + }, + "issuanceDate": "2010-01-01T00:00:00Z", + "name": "Teamwork Badge", + "credentialSubject": { + "id": "did:example:ebfeb1f712ebc6f1c276e12ec21", + "type": [ + "AchievementSubject" + ], + "achievement": { + "id": "https://example.com/achievements/21st-century-skills/teamwork", + "type": [ + "Achievement" + ], + "criteria": { + "narrative": "Team members are nominated for this badge by their peers and recognized upon review by Example Corp management." + }, + "description": "This badge recognizes the development of the capacity to collaborate within a group environment.", + "name": "Teamwork" + } + }, + "proof": [ + { + "type": "Ed25519Signature2020", + "created": "2022-11-16T18:54:22Z", + "verificationMethod": "z6MknNHHrBzPytzu6CUBP9Lg7fg4KSBjzimc2Frh693YbMiv", + "proofPurpose": "assertionMethod", + "proofValue": "z5gJZKchSJEYPGeq6bsqiLKuxT6mXqAovPbqYX66CB7u9CSNFdV41vHtysjHFiitvoyhfPxsaZnWftrZZZW2txPQK" + } + ] +} \ No newline at end of file diff --git a/inspector-vc/src/test/resources/ob30/simple-err-proof-method-unknown-did-method.json b/inspector-vc/src/test/resources/ob30/simple-err-proof-method-unknown-did-method.json new file mode 100644 index 0000000..60601f2 --- /dev/null +++ b/inspector-vc/src/test/resources/ob30/simple-err-proof-method-unknown-did-method.json @@ -0,0 +1,47 @@ +{ + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://purl.imsglobal.org/spec/ob/v3p0/context.json", + "https://w3id.org/security/suites/ed25519-2020/v1" + ], + "id": "http://example.com/credentials/3527", + "type": [ + "VerifiableCredential", + "OpenBadgeCredential" + ], + "issuer": { + "id": "https://example.com/issuers/876543", + "type": [ + "Profile" + ], + "name": "Example Corp" + }, + "issuanceDate": "2010-01-01T00:00:00Z", + "name": "Teamwork Badge", + "credentialSubject": { + "id": "did:example:ebfeb1f712ebc6f1c276e12ec21", + "type": [ + "AchievementSubject" + ], + "achievement": { + "id": "https://example.com/achievements/21st-century-skills/teamwork", + "type": [ + "Achievement" + ], + "criteria": { + "narrative": "Team members are nominated for this badge by their peers and recognized upon review by Example Corp management." + }, + "description": "This badge recognizes the development of the capacity to collaborate within a group environment.", + "name": "Teamwork" + } + }, + "proof": [ + { + "type": "Ed25519Signature2020", + "created": "2022-11-16T18:54:22Z", + "verificationMethod": "did:example:z6MknNHHrBzPytzu6CUBP9Lg7fg4KSBjzimc2Frh693YbMiv", + "proofPurpose": "assertionMethod", + "proofValue": "z5gJZKchSJEYPGeq6bsqiLKuxT6mXqAovPbqYX66CB7u9CSNFdV41vHtysjHFiitvoyhfPxsaZnWftrZZZW2txPQK" + } + ] +} \ No newline at end of file diff --git a/inspector-vc/src/test/resources/ob30/simple-err-proof-method-unknown-scheme.json b/inspector-vc/src/test/resources/ob30/simple-err-proof-method-unknown-scheme.json new file mode 100644 index 0000000..bb11ff3 --- /dev/null +++ b/inspector-vc/src/test/resources/ob30/simple-err-proof-method-unknown-scheme.json @@ -0,0 +1,47 @@ +{ + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://purl.imsglobal.org/spec/ob/v3p0/context.json", + "https://w3id.org/security/suites/ed25519-2020/v1" + ], + "id": "http://example.com/credentials/3527", + "type": [ + "VerifiableCredential", + "OpenBadgeCredential" + ], + "issuer": { + "id": "https://example.com/issuers/876543", + "type": [ + "Profile" + ], + "name": "Example Corp" + }, + "issuanceDate": "2010-01-01T00:00:00Z", + "name": "Teamwork Badge", + "credentialSubject": { + "id": "did:example:ebfeb1f712ebc6f1c276e12ec21", + "type": [ + "AchievementSubject" + ], + "achievement": { + "id": "https://example.com/achievements/21st-century-skills/teamwork", + "type": [ + "Achievement" + ], + "criteria": { + "narrative": "Team members are nominated for this badge by their peers and recognized upon review by Example Corp management." + }, + "description": "This badge recognizes the development of the capacity to collaborate within a group environment.", + "name": "Teamwork" + } + }, + "proof": [ + { + "type": "Ed25519Signature2020", + "created": "2022-11-16T18:54:22Z", + "verificationMethod": "xxx:z6MknNHHrBzPytzu6CUBP9Lg7fg4KSBjzimc2Frh693YbMiv", + "proofPurpose": "assertionMethod", + "proofValue": "z5gJZKchSJEYPGeq6bsqiLKuxT6mXqAovPbqYX66CB7u9CSNFdV41vHtysjHFiitvoyhfPxsaZnWftrZZZW2txPQK" + } + ] +} \ No newline at end of file From e096f1580b596f2b22d1d0c5f422b25ee3538355 Mon Sep 17 00:00:00 2001 From: "Andy Miller (IMS)" <48326098+amiller-ims@users.noreply.github.com> Date: Wed, 16 Nov 2022 12:45:50 -0800 Subject: [PATCH 2/2] Add valid did:key example --- .../org/oneedtech/inspect/vc/OB30Tests.java | 1 - .../resources/ob30/simple-did-method.json | 72 ++++++++----------- 2 files changed, 29 insertions(+), 44 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 7778f6f..036eb9e 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 @@ -41,7 +41,6 @@ public class OB30Tests { } @Test - @Disabled("Do not have a valid sample") void testSimpleDidMethodJsonValid() { assertDoesNotThrow(()->{ Report report = validator.run(Samples.OB30.JSON.SIMPLE_DID_METHOD_JSON.asFileResource()); diff --git a/inspector-vc/src/test/resources/ob30/simple-did-method.json b/inspector-vc/src/test/resources/ob30/simple-did-method.json index 84fb747..764298f 100644 --- a/inspector-vc/src/test/resources/ob30/simple-did-method.json +++ b/inspector-vc/src/test/resources/ob30/simple-did-method.json @@ -1,47 +1,33 @@ { - "@context": [ - "https://www.w3.org/2018/credentials/v1", - "https://purl.imsglobal.org/spec/ob/v3p0/context.json", - "https://w3id.org/security/suites/ed25519-2020/v1" - ], - "id": "http://example.com/credentials/3527", - "type": [ - "VerifiableCredential", - "OpenBadgeCredential" - ], - "issuer": { - "id": "https://example.com/issuers/876543", - "type": [ - "Profile" - ], - "name": "Example Corp" + "@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": "2010-01-01T00:00:00Z", - "name": "Teamwork Badge", - "credentialSubject": { - "id": "did:example:ebfeb1f712ebc6f1c276e12ec21", - "type": [ - "AchievementSubject" - ], - "achievement": { - "id": "https://example.com/achievements/21st-century-skills/teamwork", - "type": [ - "Achievement" - ], - "criteria": { - "narrative": "Team members are nominated for this badge by their peers and recognized upon review by Example Corp management." - }, - "description": "This badge recognizes the development of the capacity to collaborate within a group environment.", - "name": "Teamwork" + "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-16T18:54:22Z", - "verificationMethod": "did:key:z6MknNHHrBzPytzu6CUBP9Lg7fg4KSBjzimc2Frh693YbMiv", - "proofPurpose": "assertionMethod", - "proofValue": "z5gJZKchSJEYPGeq6bsqiLKuxT6mXqAovPbqYX66CB7u9CSNFdV41vHtysjHFiitvoyhfPxsaZnWftrZZZW2txPQK" - } - ] -} \ No newline at end of file + "proof" : [ { + "type" : "Ed25519Signature2020", + "created" : "2022-11-16T20:39:53Z", + "proofPurpose" : "assertionMethod", + "verificationMethod" : "did:key:z6MkwAQmEfso8UjHJZTQajRtqR5hDxAD95iJD4z53XnKCFms", + "proofValue" : "z361ueyGzREPvsWdnWUfkzTKXEd6u2DPPu2kDw3pDERJmzDFCqsuaPneqcRgz2hk9ycaNDYmC4Fy9c6S6BDDt5fVB" + } ] +}