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"); } } }