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