6.7. Verify the chain yourself

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