diff --git a/inspector-vc/pom.xml b/inspector-vc/pom.xml index 3e4ca11..b8684e5 100644 --- a/inspector-vc/pom.xml +++ b/inspector-vc/pom.xml @@ -35,7 +35,14 @@ org.springframework spring-core 5.0.12.RELEASE - + + + com.google + bitcoinj + 0.11.3 + + + com.apicatalog diff --git a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/ProofVerifierProbe.java b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/ProofVerifierProbe.java index e3073aa..4863185 100644 --- a/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/ProofVerifierProbe.java +++ b/inspector-vc/src/main/java/org/oneedtech/inspect/vc/probe/ProofVerifierProbe.java @@ -4,14 +4,28 @@ import static org.oneedtech.inspect.core.probe.RunContext.Key.JACKSON_OBJECTMAPP import java.io.ByteArrayOutputStream; import java.io.StringReader; +import java.nio.charset.StandardCharsets; import java.security.KeyFactory; +import java.security.MessageDigest; +import java.security.PublicKey; import java.security.Security; import java.security.Signature; import java.security.spec.X509EncodedKeySpec; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Base64; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Base64.Encoder; +import java.util.Map.Entry; import org.bouncycastle.asn1.edec.EdECObjectIdentifiers; import org.bouncycastle.asn1.x509.AlgorithmIdentifier; import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; +import org.bouncycastle.crypto.Signer; +import org.bouncycastle.crypto.signers.Ed25519Signer; +import org.bouncycastle.crypto.signers.RSADigestSigner; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.util.encoders.Hex; import org.oneedtech.inspect.core.probe.Probe; @@ -28,6 +42,8 @@ import com.apicatalog.rdf.RdfDataset; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; +import com.google.bitcoin.core.Base58; +import com.google.common.io.BaseEncoding; import io.setl.rdf.normalization.RdfNormalize; @@ -45,9 +61,42 @@ public class ProofVerifierProbe extends Probe { public ReportItems run(Credential crd, RunContext ctx) throws Exception { try { - String canonical = canonicalize(crd, C14n.URDNA2015, MediaType.N_QUADS, ctx); + //String document = fetchConanicalDocument(crd, C14n.URDNA2015, MediaType.N_QUADS, ctx); + String document = ""; + String proof = fetchConanicalProof(crd, C14n.URDNA2015, MediaType.N_QUADS, ctx); //System.out.println(canonical); + + + + /* + Encoder encoder1 = Base64.getEncoder(); + String testSignature = "z3MUt2ZuU8Byqivxh6GphEM65AFYyNaGYibm97xLTafM7uGufZQLKvJR8itZwxKskvtFM3CUty46v26DZidMNoQnM"; + String signature = encoder1.encodeToString(testSignature.getBytes()); + + Encoder encoder2 = Base64.getEncoder(); + String testKey = "z6MkkUD3J14nkYzn46QeuaVSnp7dF85QJKwKvJvfsjx79aXj"; + String key64 = encoder2.encodeToString(testKey.getBytes()); + String keyHex = Hex.toHexString(testKey.getBytes()); + + boolean test = validate( + keyHex, + signature, + "", + canonical + ); + */ + + + byte[] docHash = getBytes(document); + byte[] proofHash = getBytes(proof); + // concatenate hash of c14n proof options and hash of c14n document + byte[] combined = mergeArrays(proofHash, docHash); + + boolean test = testSigner(combined); + + + boolean stophere = true; //TODO if proofs fail, report OutCome.Fatal //return fatal("msg", ctx); @@ -57,7 +106,7 @@ public class ProofVerifierProbe extends Probe { return success(ctx); } - private String canonicalize(Credential crd, C14n algo, MediaType mediaType, RunContext ctx) throws Exception { + private String fetchConanicalDocument(Credential crd, C14n algo, MediaType mediaType, RunContext ctx) throws Exception { //clone the incoming credential object so we can modify it freely ObjectMapper mapper = (ObjectMapper)ctx.get(JACKSON_OBJECTMAPPER); @@ -81,6 +130,55 @@ public class ProofVerifierProbe extends Probe { return result; } + private String fetchConanicalProof(Credential crd, C14n algo, MediaType mediaType, RunContext ctx) throws Exception { + + //clone the incoming credential object so we can modify it freely + ObjectMapper mapper = (ObjectMapper)ctx.get(JACKSON_OBJECTMAPPER); + JsonNode copy = mapper.readTree(crd.asJson().toString()); + + //Get the context node to stitch back in. + JsonNode context = copy.get("@context"); + + //Pull out and use proof node only + JsonNode proof = copy.get("proof"); + + //TODO: Make this better at discarding all, but the linked data proof method. + if(proof.isArray()){ + proof = proof.get(0); + } + + //remove these if they exist + ((ObjectNode)proof).remove("jwt"); + ((ObjectNode)proof).remove("signatureValue"); + ((ObjectNode)proof).remove("proofValue"); + + JsonNode newNode = mapper.createObjectNode(); + ((ObjectNode) newNode).set("@context", context); + //Try to structure this 'to the letter' per a slack with Markus + //((ObjectNode) newNode).set("proof", proof); + + //So that we don't remove nodes while iterating over it save all nodes + Iterator> iter = proof.fields(); + while (iter.hasNext()) { + Entry next = iter.next(); + ((ObjectNode) newNode).set(next.getKey(), next.getValue()); + } + + //create JSON-P Json-LD instance + JsonDocument jsonLdDoc = JsonDocument.of(new StringReader(newNode.toString())); + + //create rdf and normalize + RdfDataset dataSet = JsonLd.toRdf(jsonLdDoc).ordered(true).get(); + RdfDataset normalized = RdfNormalize.normalize(dataSet); + + //serialize to string + ByteArrayOutputStream os = new ByteArrayOutputStream(); + Rdf.createWriter(mediaType, os).write(normalized); + String result = StringUtils.stripTrailing(os.toString()); + + return result; + } + private boolean validate(String pubkey, String signature, String timestamp, String message) throws Exception { //TODO: continue this implementation. //Pulled in bouncy castle library and made sure this sample compiled only. @@ -93,10 +191,140 @@ public class ProofVerifierProbe extends Probe { final var publicKey = kf.generatePublic(pkSpec); final var signedData = Signature.getInstance("ed25519", provider); signedData.initVerify(publicKey); + //Temporarily remove timestamp signedData.update(timestamp.getBytes()); signedData.update(message.getBytes()); return signedData.verify(Hex.decode(signature)); } + + private boolean testSigner(byte[] concatBytes) throws Exception { + + + final var provider = new BouncyCastleProvider(); + Security.addProvider(provider); + + //var publicKeyBytes = Base64.getUrlDecoder().decode("z6MkkUD3J14nkYzn46QeuaVSnp7dF85QJKwKvJvfsjx79aXj"); + //var publicKeyBytes = Base64.getUrlDecoder().decode("6MkkUD3J14nkYzn46QeuaVSnp7dF85QJKwKvJvfsjx79aXj"); + + + //var publicKeyBytes = Base58.decode("z6MkkUD3J14nkYzn46QeuaVSnp7dF85QJKwKvJvfsjx79aXj"); + //Key with the first chracter stripped + //var publicKeyBytes = Base58.decode("6MkkUD3J14nkYzn46QeuaVSnp7dF85QJKwKvJvfsjx79aXj"); + + + //A working sample key + //var publicKeyBytes = Base64.getUrlDecoder().decode("11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo"); + + + + + //Base 58 decode minus the z + var publicKeyBytes = Base58.decode("6MkkUD3J14nkYzn46QeuaVSnp7dF85QJKwKvJvfsjx79aXj"); + //The slice out the first two array elements (???) + byte[] slicedArray = Arrays.copyOfRange(publicKeyBytes, 2, 34); + + + + final var pki = new SubjectPublicKeyInfo(new AlgorithmIdentifier(EdECObjectIdentifiers.id_Ed25519), slicedArray); + final var pkSpec = new X509EncodedKeySpec(pki.getEncoded()); + final var kf = KeyFactory.getInstance("ed25519", provider); + final var publicKey = kf.generatePublic(pkSpec); + final var signedData = Signature.getInstance("ed25519", provider); + signedData.initVerify(publicKey); + signedData.update(concatBytes); + + boolean whatever = true; + + + //Final step, add signature. + + //Need to do this in java + //const signatureBytes = base58btc.decode(proofValue.substr(1)); + + + var signatureBytes = Base58.decode("3MUt2ZuU8Byqivxh6GphEM65AFYyNaGYibm97xLTafM7uGufZQLKvJR8itZwxKskvtFM3CUty46v26DZidMNoQnM"); + + return signedData.verify(signatureBytes); + + + + +/* + String hexEncodedPubKey = "z6MkkUD3J14nkYzn46QeuaVSnp7dF85QJKwKvJvfsjx79aXj"; + + // Convert to JCA format + byte[] pubKeyBytes = BaseEncoding.base16().lowerCase().decode(hexEncodedPubKey); + SubjectPublicKeyInfo pubKeyInfo = new SubjectPublicKeyInfo( + new AlgorithmIdentifier(EdECObjectIdentifiers.id_Ed25519), pubKeyBytes); + + + X509EncodedKeySpec keySpec = new X509EncodedKeySpec(pubKeyInfo.getEncoded()); + KeyFactory keyFactory = KeyFactory.getInstance("Ed25519", provider); + PublicKey pk = keyFactory.generatePublic(keySpec); + + +*/ + + + + + /* + var test = new RSADigestSigner(digest, digestOid) + + test.verifySignature(signature); + */ + + + + + + + } + + /* + private boolean testSigner3(String message, byte[] concateBytes) throws Exception { + + + // Test case defined in https://www.rfc-editor.org/rfc/rfc8037 + var msg = "eyJhbGciOiJFZERTQSJ9.RXhhbXBsZSBvZiBFZDI1NTE5IHNpZ25pbmc".getBytes(StandardCharsets.UTF_8); + var expectedSig = "hgyY0il_MGCjP0JzlnLWG1PPOt7-09PGcvMg3AIbQR6dWbhijcNR4ki4iylGjg5BhVsPt9g7sVvpAr_MuM0KAg"; + + var privateKeyBytes = Base64.getUrlDecoder().decode("nWGxne_9WmC6hEr0kuwsxERJxWl7MmkZcDusAxyuf2A"); + var publicKeyBytes = Base64.getUrlDecoder().decode("11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo"); + + var privateKey = new Ed25519PrivateKeyParameters(privateKeyBytes, 0); + var publicKey = new Ed25519PublicKeyParameters(publicKeyBytes, 0); + + // Generate new signature + Signer signer = new Ed25519Signer(); + signer.init(true, privateKey); + signer.update(msg, 0, msg.length); + byte[] signature = signer.generateSignature(); + var actualSignature = Base64.getUrlEncoder().encodeToString(signature).replace("=", ""); + + LOG.info("Expected signature: {}", expectedSig); + LOG.info("Actual signature : {}", actualSignature); + + assertEquals(expectedSig, actualSignature); + + + return true; + } + */ + + + private byte[] getBytes(String value) throws Exception{ + MessageDigest digest = MessageDigest.getInstance("SHA-256"); + return digest.digest( + value.getBytes(StandardCharsets.UTF_8) + ); + } + + private static byte[] mergeArrays(final byte[] array1, byte[] array2) { + byte[] joinedArray = Arrays.copyOf(array1, array1.length + array2.length); + System.arraycopy(array2, 0, joinedArray, array1.length, array2.length); + return joinedArray; + } private enum C14n { URDNA2015 diff --git a/inspector-vc/src/test/resources/ob30/simple-old-delete.json b/inspector-vc/src/test/resources/ob30/simple-old-delete.json deleted file mode 100644 index 7795327..0000000 --- a/inspector-vc/src/test/resources/ob30/simple-old-delete.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "@context": [ - "https://www.w3.org/2018/credentials/v1", - "https://imsglobal.github.io/openbadges-specification/context.json", - "https://w3id.org/security/suites/ed25519-2020/v1" - ], - "id": "http://example.edu/credentials/3732", - "type": [ - "VerifiableCredential", - "OpenBadgeCredential" - ], - "issuer": { - "id": "https://example.edu/issuers/565049", - "type": [ - "Profile" - ], - "name": "Example University" - }, - "issuanceDate": "2010-01-01T00:00:00Z", - "name": "Example University Degree", - "credentialSubject": { - "id": "did:example:ebfeb1f712ebc6f1c276e12ec21", - "type": [ - "AchievementSubject" - ] - }, - "proof": [ - { - "type": "Ed25519Signature2020", - "created": "2022-06-09T22:56:28Z", - "verificationMethod": "https://example.edu/issuers/565049#key-1", - "proofPurpose": "assertionMethod", - "proofValue": "z58ieJCh4kN6eE2Vq4TyYURKSC4hWWEK7b75NNUL2taZMhKqwTteuByG1wRoGDdCqqNLW5Gq1diUi4qyZ63tQRtyN" - } - ] - } \ No newline at end of file