mirror of
https://github.com/Qortal/qortal.git
synced 2025-07-22 20:26:50 +00:00
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:
@@ -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();
|
||||
}
|
||||
|
||||
|
77
src/main/java/org/qortal/arbitrary/ArbitraryDataDigest.java
Normal file
77
src/main/java/org/qortal/arbitrary/ArbitraryDataDigest.java
Normal 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);
|
||||
}
|
||||
|
||||
}
|
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
Reference in New Issue
Block a user