To verify the message yourself, you will need 3 set of files. First, you need the secure chain chunk containing the message, then you need the corresponding timestamp and finally the manual signature of the chunk. Contact support@babelway.com to receive theses files.
The following java code, using bouncycastle and CSVReader, can be used to validate the chain.
package com.babelway.tools;
import java.io.IOException;
import java.io.StringReader;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.Security;
import java.util.Base64;
import java.util.Collection;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cms.CMSProcessableByteArray;
import org.bouncycastle.cms.CMSSignedData;
import org.bouncycastle.cms.CMSSignerDigestMismatchException;
import org.bouncycastle.cms.CMSVerifierCertificateNotValidException;
import org.bouncycastle.cms.SignerInformation;
import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoVerifierBuilder;
import org.bouncycastle.jcajce.provider.digest.SHA3;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.util.encoders.Hex;
import au.com.bytecode.opencsv.CSVReader;
public class SecureChainValidator {
public static void main(String[] args) throws Exception {
Security.addProvider(new BouncyCastleProvider());
// # 1. Validate that the part of the secure chain (i.e. the chunk) that you want to verify is self-consistant
// The CSV file that contains a part of the secure chain that you want to verify.
byte[] chunk = Files.readAllBytes(Paths.get("/tmp/chunk.csv"));
assertChunkFileIsSelfConsistent(chunk);
System.out.println("The chunk is self consistant!");
// # 2. Validate the related timestamp
// The timestamp
byte[] timestamp = Files.readAllBytes(Paths.get("/tmp/timestamp.txt"));
assertTimestampIsValid(timestamp);
System.out.println("The timstamp is valid!");
// 2. Validate the related manual signature
// The text file (in JSON format) that contains all the information about all the chunks that
// were manually signed together, including the target chunk
byte[] signatureContent = Files.readAllBytes(Paths.get("/tmp/signatureContent.txt"));
// The signature itself
byte[] signature = Files.readAllBytes(Paths.get("/tmp/signature.txt"));
assertSignatureIsValid(signature, signatureContent);
System.out.println("The signature is valid!");
System.out.println("Verification finished successfully");
}
public static void assertChunkFileIsSelfConsistent(byte[] chunk) {
try (CSVReader reader = new CSVReader(new StringReader(new String(chunk)), ';', '"', '\\')) {
String[] row = reader.readNext();
if (row == null) {
throw new IllegalArgumentException("Empty chunk file");
}
//chaining = chainId;creationMoment;hubId;messageKey;step;hash;previousChainHash
String chainHash = row[6];
StringBuilder sb;
while ((row = reader.readNext()) != null) {
sb = new StringBuilder();
for (int j = 0; j < 6; j ) {
sb.append(row[j]).append(';');
}
sb.append(chainHash);
chainHash = hash(sb.toString().getBytes());
if (!chainHash.equals(row[6])) {
throw new RuntimeException("Chunk is not consitant: expected chain hash " chainHash);
}
}
} catch (IOException e) {
throw new RuntimeException("Unable to read chunk content");
}
}
public static String hash(byte[] bytes) {
if (bytes == null) {
throw new IllegalArgumentException("Argument should not be null");
}
try {
SHA3.DigestSHA3 md = new SHA3.Digest512();
return new String(Hex.encode(md.digest(bytes)), "UTF8");
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static void assertTimestampIsValid(byte[] timestamp) {
try {
CMSSignedData cmsSignedData = new CMSSignedData(Base64.getDecoder().decode(new String(timestamp)));
SignerInformation timestampSigner = cmsSignedData.getSignerInfos().getSigners().iterator().next();
X509CertificateHolder timestampSigningCertificate = getSignerCertificate(cmsSignedData, timestampSigner);
if (!timestampSigner.verify(new JcaSimpleSignerInfoVerifierBuilder().setProvider(BouncyCastleProvider.PROVIDER_NAME).build(timestampSigningCertificate))) {
throw new RuntimeException("Timestamp status is not OK");
}
} catch (Exception e) {
throw new RuntimeException("Unable to validate timestamp: " e.getMessage(), e);
}
}
public static void assertSignatureIsValid(byte[] signature, byte[] signatureContent) {
try {
CMSSignedData signedData = new CMSSignedData(new CMSProcessableByteArray(signatureContent), Base64.getDecoder().decode(new String(signature)));
SignerInformation signer = signedData.getSignerInfos().getSigners().iterator().next();
X509CertificateHolder signerCertificate = getSignerCertificate(signedData, signer);
if (!signer.verify((new JcaSimpleSignerInfoVerifierBuilder()).setProvider("BC").build(signerCertificate))) {
throw new RuntimeException("Signature is not valid");
}
} catch (CMSVerifierCertificateNotValidException | CMSSignerDigestMismatchException e) {
throw new RuntimeException("Signature is not valid", e);
} catch (Exception e) {
throw new RuntimeException("Unable to verify signature.", e);
}
}
private static X509CertificateHolder getSignerCertificate(CMSSignedData signedData, SignerInformation signerInfo) {
Collection matches = signedData.getCertificates().getMatches(signerInfo.getSID());
if (matches != null && !matches.isEmpty()) {
return (X509CertificateHolder) matches.iterator().next();
} else {
throw new RuntimeException("No certificate found for the signer");
}
}
}