Hash the current state when creating a patch

This is included in the patch metadata and then validated every time it is rebuilt.
This commit is contained in:
CalDescent 2021-10-24 13:00:21 +01:00
parent 9cd579d3db
commit a418fb18b6
4 changed files with 84 additions and 22 deletions

View File

@ -24,7 +24,7 @@ public class ArbitraryDataCombiner {
private Path pathAfter; private Path pathAfter;
private byte[] signatureBefore; private byte[] signatureBefore;
private Path finalPath; private Path finalPath;
ArbitraryDataMetadataPatch metadata; private ArbitraryDataMetadataPatch metadata;
public ArbitraryDataCombiner(Path pathBefore, Path pathAfter, byte[] signatureBefore) { public ArbitraryDataCombiner(Path pathBefore, Path pathAfter, byte[] signatureBefore) {
this.pathBefore = pathBefore; this.pathBefore = pathBefore;
@ -39,6 +39,7 @@ public class ArbitraryDataCombiner {
this.validatePreviousSignature(); this.validatePreviousSignature();
this.validatePreviousHash(); this.validatePreviousHash();
this.process(); this.process();
this.validateCurrentHash();
} finally { } finally {
this.postExecute(); this.postExecute();
@ -132,6 +133,22 @@ public class ArbitraryDataCombiner {
this.finalPath = merge.getMergePath(); this.finalPath = merge.getMergePath();
} }
private void validateCurrentHash() throws IOException {
byte[] currentHash = this.metadata.getCurrentHash();
if (currentHash == null) {
throw new IllegalStateException("Unable to extract current hash from patch metadata");
}
ArbitraryDataDigest digest = new ArbitraryDataDigest(this.finalPath);
digest.compute();
boolean valid = digest.isHashValid(currentHash);
if (!valid) {
String currentHash58 = Base58.encode(currentHash);
throw new InvalidObjectException(String.format("Current state hash mismatch. " +
"Patch curHash: %s, actual: %s", currentHash58, digest.getHash58()));
}
}
public Path getFinalPath() { public Path getFinalPath() {
return this.finalPath; return this.finalPath;
} }

View File

@ -65,6 +65,7 @@ public class ArbitraryDataDiff {
private Path pathAfter; private Path pathAfter;
private byte[] previousSignature; private byte[] previousSignature;
private byte[] previousHash; private byte[] previousHash;
private byte[] currentHash;
private Path diffPath; private Path diffPath;
private String identifier; private String identifier;
@ -92,6 +93,7 @@ public class ArbitraryDataDiff {
this.findAddedOrModifiedFiles(); this.findAddedOrModifiedFiles();
this.findRemovedFiles(); this.findRemovedFiles();
this.validate(); this.validate();
this.hashCurrentState();
this.writeMetadata(); this.writeMetadata();
} finally { } finally {
@ -272,6 +274,12 @@ public class ArbitraryDataDiff {
} }
} }
private void hashCurrentState() throws IOException {
ArbitraryDataDigest digest = new ArbitraryDataDigest(this.pathAfter);
digest.compute();
this.currentHash = digest.getHash();
}
private void writeMetadata() throws IOException { private void writeMetadata() throws IOException {
ArbitraryDataMetadataPatch metadata = new ArbitraryDataMetadataPatch(this.diffPath); ArbitraryDataMetadataPatch metadata = new ArbitraryDataMetadataPatch(this.diffPath);
metadata.setAddedPaths(this.addedPaths); metadata.setAddedPaths(this.addedPaths);
@ -279,6 +287,7 @@ public class ArbitraryDataDiff {
metadata.setRemovedPaths(this.removedPaths); metadata.setRemovedPaths(this.removedPaths);
metadata.setPreviousSignature(this.previousSignature); metadata.setPreviousSignature(this.previousSignature);
metadata.setPreviousHash(this.previousHash); metadata.setPreviousHash(this.previousHash);
metadata.setCurrentHash(this.currentHash);
metadata.write(); metadata.write();
} }

View File

@ -23,6 +23,7 @@ public class ArbitraryDataMetadataPatch extends ArbitraryDataMetadata {
private List<Path> removedPaths; private List<Path> removedPaths;
private byte[] previousSignature; private byte[] previousSignature;
private byte[] previousHash; private byte[] previousHash;
private byte[] currentHash;
public ArbitraryDataMetadataPatch(Path filePath) { public ArbitraryDataMetadataPatch(Path filePath) {
super(filePath); super(filePath);
@ -56,6 +57,12 @@ public class ArbitraryDataMetadataPatch extends ArbitraryDataMetadata {
this.previousHash = Base58.decode(prevHash); this.previousHash = Base58.decode(prevHash);
} }
} }
if (patch.has("curHash")) {
String curHash = patch.getString("curHash");
if (curHash != null) {
this.currentHash = Base58.decode(curHash);
}
}
if (patch.has("added")) { if (patch.has("added")) {
JSONArray added = (JSONArray) patch.get("added"); JSONArray added = (JSONArray) patch.get("added");
if (added != null) { if (added != null) {
@ -101,6 +108,7 @@ public class ArbitraryDataMetadataPatch extends ArbitraryDataMetadata {
patch.put("prevSig", Base58.encode(this.previousSignature)); patch.put("prevSig", Base58.encode(this.previousSignature));
patch.put("prevHash", Base58.encode(this.previousHash)); patch.put("prevHash", Base58.encode(this.previousHash));
patch.put("curHash", Base58.encode(this.currentHash));
patch.put("added", new JSONArray(this.addedPaths)); patch.put("added", new JSONArray(this.addedPaths));
patch.put("removed", new JSONArray(this.removedPaths)); patch.put("removed", new JSONArray(this.removedPaths));
@ -157,4 +165,12 @@ public class ArbitraryDataMetadataPatch extends ArbitraryDataMetadata {
return this.previousHash; return this.previousHash;
} }
public void setCurrentHash(byte[] currentHash) {
this.currentHash = currentHash;
}
public byte[] getCurrentHash() {
return this.currentHash;
}
} }

View File

@ -2,9 +2,9 @@ package org.qortal.test.arbitrary;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.qortal.arbitrary.ArbitraryDataCombiner;
import org.qortal.arbitrary.ArbitraryDataCreatePatch; import org.qortal.arbitrary.ArbitraryDataCreatePatch;
import org.qortal.arbitrary.ArbitraryDataDigest; import org.qortal.arbitrary.ArbitraryDataDigest;
import org.qortal.arbitrary.ArbitraryDataMerge;
import org.qortal.crypto.Crypto; import org.qortal.crypto.Crypto;
import org.qortal.repository.DataException; import org.qortal.repository.DataException;
import org.qortal.test.common.Common; import org.qortal.test.common.Common;
@ -35,8 +35,12 @@ public class ArbitraryDataMergeTests extends Common {
Path path1 = Paths.get("src/test/resources/arbitrary/demo1"); Path path1 = Paths.get("src/test/resources/arbitrary/demo1");
Path path2 = Paths.get("src/test/resources/arbitrary/demo2"); Path path2 = Paths.get("src/test/resources/arbitrary/demo2");
// Generate random signature for the purposes of validation
byte[] signature = new byte[32];
new Random().nextBytes(signature);
// Create a patch using the differences in path2 compared with path1 // Create a patch using the differences in path2 compared with path1
ArbitraryDataCreatePatch patch = new ArbitraryDataCreatePatch(path1, path2, new byte[16]); ArbitraryDataCreatePatch patch = new ArbitraryDataCreatePatch(path1, path2, signature);
patch.create(); patch.create();
Path patchPath = patch.getFinalPath(); Path patchPath = patch.getFinalPath();
assertTrue(Files.exists(patchPath)); assertTrue(Files.exists(patchPath));
@ -90,9 +94,9 @@ public class ArbitraryDataMergeTests extends Common {
); );
// Now merge the patch with the original path // Now merge the patch with the original path
ArbitraryDataMerge merge = new ArbitraryDataMerge(path1, patchPath); ArbitraryDataCombiner combiner = new ArbitraryDataCombiner(path1, patchPath, signature);
merge.compute(); combiner.combine();
Path finalPath = merge.getMergePath(); Path finalPath = combiner.getFinalPath();
// Ensure that all files exist in the final path (including lorem3) // Ensure that all files exist in the final path (including lorem3)
assertTrue(Files.exists(Paths.get(finalPath.toString(), "lorem1.txt"))); assertTrue(Files.exists(Paths.get(finalPath.toString(), "lorem1.txt")));
@ -185,8 +189,12 @@ public class ArbitraryDataMergeTests extends Common {
assertTrue(Files.exists(file1.toPath())); assertTrue(Files.exists(file1.toPath()));
assertTrue(Files.exists(file2.toPath())); assertTrue(Files.exists(file2.toPath()));
// Generate random signature for the purposes of validation
byte[] signature = new byte[32];
new Random().nextBytes(signature);
// Create a patch from the two paths // Create a patch from the two paths
ArbitraryDataCreatePatch patch = new ArbitraryDataCreatePatch(tempDir1, tempDir2, new byte[16]); ArbitraryDataCreatePatch patch = new ArbitraryDataCreatePatch(tempDir1, tempDir2, signature);
patch.create(); patch.create();
Path patchPath = patch.getFinalPath(); Path patchPath = patch.getFinalPath();
assertTrue(Files.exists(patchPath)); assertTrue(Files.exists(patchPath));
@ -205,9 +213,9 @@ public class ArbitraryDataMergeTests extends Common {
assertFalse(Arrays.equals(patchDigest, file1Digest)); assertFalse(Arrays.equals(patchDigest, file1Digest));
// Now merge the patch with the original path // Now merge the patch with the original path
ArbitraryDataMerge merge = new ArbitraryDataMerge(tempDir1, patchPath); ArbitraryDataCombiner combiner = new ArbitraryDataCombiner(tempDir1, patchPath, signature);
merge.compute(); combiner.combine();
Path finalPath = merge.getMergePath(); Path finalPath = combiner.getFinalPath();
// Check that the directory digests match // Check that the directory digests match
ArbitraryDataDigest path2Digest = new ArbitraryDataDigest(tempDir2); ArbitraryDataDigest path2Digest = new ArbitraryDataDigest(tempDir2);
@ -252,8 +260,12 @@ public class ArbitraryDataMergeTests extends Common {
assertTrue(Files.exists(file1.toPath())); assertTrue(Files.exists(file1.toPath()));
assertTrue(Files.exists(file2.toPath())); assertTrue(Files.exists(file2.toPath()));
// Generate random signature for the purposes of validation
byte[] signature = new byte[32];
new Random().nextBytes(signature);
// Create a patch from the two paths // Create a patch from the two paths
ArbitraryDataCreatePatch patch = new ArbitraryDataCreatePatch(tempDir1, tempDir2, new byte[16]); ArbitraryDataCreatePatch patch = new ArbitraryDataCreatePatch(tempDir1, tempDir2, signature);
patch.create(); patch.create();
Path patchPath = patch.getFinalPath(); Path patchPath = patch.getFinalPath();
assertTrue(Files.exists(patchPath)); assertTrue(Files.exists(patchPath));
@ -268,9 +280,9 @@ public class ArbitraryDataMergeTests extends Common {
assertFalse(Arrays.equals(patchDigest, file2Digest)); assertFalse(Arrays.equals(patchDigest, file2Digest));
// Now merge the patch with the original path // Now merge the patch with the original path
ArbitraryDataMerge merge = new ArbitraryDataMerge(tempDir1, patchPath); ArbitraryDataCombiner combiner = new ArbitraryDataCombiner(tempDir1, patchPath, signature);
merge.compute(); combiner.combine();
Path finalPath = merge.getMergePath(); Path finalPath = combiner.getFinalPath();
// Check that the directory digests match // Check that the directory digests match
ArbitraryDataDigest path2Digest = new ArbitraryDataDigest(tempDir2); ArbitraryDataDigest path2Digest = new ArbitraryDataDigest(tempDir2);
@ -318,8 +330,12 @@ public class ArbitraryDataMergeTests extends Common {
assertTrue(Files.exists(file1.toPath())); assertTrue(Files.exists(file1.toPath()));
assertTrue(Files.exists(file2.toPath())); assertTrue(Files.exists(file2.toPath()));
// Generate random signature for the purposes of validation
byte[] signature = new byte[32];
new Random().nextBytes(signature);
// Create a patch from the two paths // Create a patch from the two paths
ArbitraryDataCreatePatch patch = new ArbitraryDataCreatePatch(tempDir1, tempDir2, new byte[16]); ArbitraryDataCreatePatch patch = new ArbitraryDataCreatePatch(tempDir1, tempDir2, signature);
patch.create(); patch.create();
Path patchPath = patch.getFinalPath(); Path patchPath = patch.getFinalPath();
assertTrue(Files.exists(patchPath)); assertTrue(Files.exists(patchPath));
@ -337,9 +353,9 @@ public class ArbitraryDataMergeTests extends Common {
assertFalse(Arrays.equals(patchDigest, file1Digest)); assertFalse(Arrays.equals(patchDigest, file1Digest));
// Now merge the patch with the original path // Now merge the patch with the original path
ArbitraryDataMerge merge = new ArbitraryDataMerge(tempDir1, patchPath); ArbitraryDataCombiner combiner = new ArbitraryDataCombiner(tempDir1, patchPath, signature);
merge.compute(); combiner.combine();
Path finalPath = merge.getMergePath(); Path finalPath = combiner.getFinalPath();
// Check that the directory digests match // Check that the directory digests match
ArbitraryDataDigest path2Digest = new ArbitraryDataDigest(tempDir2); ArbitraryDataDigest path2Digest = new ArbitraryDataDigest(tempDir2);
@ -385,8 +401,12 @@ public class ArbitraryDataMergeTests extends Common {
assertTrue(Files.exists(file1.toPath())); assertTrue(Files.exists(file1.toPath()));
assertTrue(Files.exists(file2.toPath())); assertTrue(Files.exists(file2.toPath()));
// Generate random signature for the purposes of validation
byte[] signature = new byte[32];
new Random().nextBytes(signature);
// Create a patch from the two paths // Create a patch from the two paths
ArbitraryDataCreatePatch patch = new ArbitraryDataCreatePatch(tempDir1, tempDir2, new byte[16]); ArbitraryDataCreatePatch patch = new ArbitraryDataCreatePatch(tempDir1, tempDir2, signature);
patch.create(); patch.create();
Path patchPath = patch.getFinalPath(); Path patchPath = patch.getFinalPath();
assertTrue(Files.exists(patchPath)); assertTrue(Files.exists(patchPath));
@ -404,9 +424,9 @@ public class ArbitraryDataMergeTests extends Common {
assertFalse(Arrays.equals(patchDigest, file1Digest)); assertFalse(Arrays.equals(patchDigest, file1Digest));
// Now merge the patch with the original path // Now merge the patch with the original path
ArbitraryDataMerge merge = new ArbitraryDataMerge(tempDir1, patchPath); ArbitraryDataCombiner combiner = new ArbitraryDataCombiner(tempDir1, patchPath, signature);
merge.compute(); combiner.combine();
Path finalPath = merge.getMergePath(); Path finalPath = combiner.getFinalPath();
// Check that the directory digests match // Check that the directory digests match
ArbitraryDataDigest path2Digest = new ArbitraryDataDigest(tempDir2); ArbitraryDataDigest path2Digest = new ArbitraryDataDigest(tempDir2);