Take a hash of the previous state's directory structure and file contents, and then include that hash in the patch metadata (when creating a new patch). This allows the integrity of the layers to be validated as each one is applied.

This commit is contained in:
CalDescent
2021-08-17 08:43:08 +01:00
parent 9baccc0784
commit 0f1927b4b1
16 changed files with 188 additions and 2 deletions

View File

@@ -22,6 +22,7 @@ public class ArbitraryDataDiff {
private Path pathBefore;
private Path pathAfter;
private byte[] previousSignature;
private byte[] previousHash;
private Path diffPath;
private String identifier;
@@ -45,6 +46,7 @@ public class ArbitraryDataDiff {
public void compute() throws IOException {
try {
this.preExecute();
this.hashPreviousState();
this.findAddedOrModifiedFiles();
this.findRemovedFiles();
this.writeMetadata();
@@ -78,6 +80,12 @@ public class ArbitraryDataDiff {
this.diffPath = tempDir;
}
private void hashPreviousState() throws IOException {
ArbitraryDataDigest digest = new ArbitraryDataDigest(this.pathBefore);
digest.compute();
this.previousHash = digest.getHash();
}
private void findAddedOrModifiedFiles() {
try {
final Path pathBeforeAbsolute = this.pathBefore.toAbsolutePath();
@@ -220,6 +228,7 @@ public class ArbitraryDataDiff {
metadata.setModifiedPaths(this.modifiedPaths);
metadata.setRemovedPaths(this.removedPaths);
metadata.setPreviousSignature(this.previousSignature);
metadata.setPreviousHash(this.previousHash);
metadata.write();
}

View File

@@ -0,0 +1,77 @@
package org.qortal.arbitrary;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.qortal.utils.Base58;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class ArbitraryDataDigest {
private static final Logger LOGGER = LogManager.getLogger(ArbitraryDataDigest.class);
private Path path;
private byte[] hash;
public ArbitraryDataDigest(Path path) {
this.path = path;
}
public void compute() throws IOException {
List<Path> allPaths = new ArrayList<>();
Files.walk(path).filter(Files::isRegularFile).forEachOrdered(p -> allPaths.add(p));
Path basePathAbsolute = this.path.toAbsolutePath();
MessageDigest sha256 = null;
try {
sha256 = MessageDigest.getInstance("SHA-256");
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException("SHA-256 hashing algorithm unavailable");
}
for (Path path : allPaths) {
// We need to work with paths relative to the base path, to ensure the same hash
// is generated on different systems
Path relativePath = basePathAbsolute.relativize(path.toAbsolutePath());
// Exclude Qortal folder since it can be different each time
// We only care about hashing the actual user data
if (relativePath.startsWith(".qortal/")) {
continue;
}
// Hash path
byte[] filePathBytes = relativePath.toString().toLowerCase().getBytes(StandardCharsets.UTF_8);
sha256.update(filePathBytes);
// Hash contents
byte[] fileContent = Files.readAllBytes(path);
sha256.update(fileContent);
}
this.hash = sha256.digest();
}
public boolean isHashValid(byte[] hash) {
return Arrays.equals(hash, this.hash);
}
public byte[] getHash() {
return this.hash;
}
public String getHash58() {
if (this.hash == null) {
return null;
}
return Base58.encode(this.hash);
}
}

View File

@@ -1,20 +1,27 @@
package org.qortal.arbitrary.metadata;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.json.JSONArray;
import org.json.JSONObject;
import org.qortal.utils.Base58;
import java.lang.reflect.Field;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
public class ArbitraryDataMetadataPatch extends ArbitraryDataMetadata {
private static final Logger LOGGER = LogManager.getLogger(ArbitraryDataMetadataPatch.class);
private List<Path> addedPaths;
private List<Path> modifiedPaths;
private List<Path> removedPaths;
private byte[] previousSignature;
private byte[] previousHash;
public ArbitraryDataMetadataPatch(Path filePath) {
super(filePath);
@@ -42,6 +49,12 @@ public class ArbitraryDataMetadataPatch extends ArbitraryDataMetadata {
this.previousSignature = Base58.decode(prevSig);
}
}
if (patch.has("prevHash")) {
String prevHash = patch.getString("prevHash");
if (prevHash != null) {
this.previousHash = Base58.decode(prevHash);
}
}
if (patch.has("added")) {
JSONArray added = (JSONArray) patch.get("added");
if (added != null) {
@@ -74,7 +87,17 @@ public class ArbitraryDataMetadataPatch extends ArbitraryDataMetadata {
@Override
protected void buildJson() {
JSONObject patch = new JSONObject();
// Attempt to use a LinkedHashMap so that the order of fields is maintained
try {
Field changeMap = patch.getClass().getDeclaredField("map");
changeMap.setAccessible(true);
changeMap.set(patch, new LinkedHashMap<>());
changeMap.setAccessible(false);
} catch (IllegalAccessException | NoSuchFieldException e) {
// Don't worry about failures as this is for ordering only
}
patch.put("prevSig", Base58.encode(this.previousSignature));
patch.put("prevHash", Base58.encode(this.previousHash));
patch.put("added", new JSONArray(this.addedPaths));
patch.put("modified", new JSONArray(this.modifiedPaths));
patch.put("removed", new JSONArray(this.removedPaths));
@@ -116,4 +139,12 @@ public class ArbitraryDataMetadataPatch extends ArbitraryDataMetadata {
return this.previousSignature;
}
public void setPreviousHash(byte[] previousHash) {
this.previousHash = previousHash;
}
public byte[] getPreviousHash() {
return this.previousHash;
}
}