package org.bouncycastle.tsp.cms;

import java.io.IOException;
import java.io.OutputStream;

import org.bouncycastle.asn1.ASN1Encoding;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.cms.AttributeTable;
import org.bouncycastle.asn1.cms.ContentInfo;
import org.bouncycastle.asn1.cms.Evidence;
import org.bouncycastle.asn1.cms.TimeStampAndCRL;
import org.bouncycastle.asn1.cms.TimeStampedData;
import org.bouncycastle.asn1.cms.TimeStampedDataParser;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.cms.CMSException;
import org.bouncycastle.operator.DigestCalculator;
import org.bouncycastle.operator.DigestCalculatorProvider;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.tsp.TSPException;
import org.bouncycastle.tsp.TimeStampToken;
import org.bouncycastle.tsp.TimeStampTokenInfo;
import org.bouncycastle.util.Arrays;

class TimeStampDataUtil
{
    private final TimeStampAndCRL[] timeStamps;

    private final MetaDataUtil      metaDataUtil;

    TimeStampDataUtil(TimeStampedData timeStampedData)
    {
        this.metaDataUtil = new MetaDataUtil(timeStampedData.getMetaData());

        Evidence evidence = timeStampedData.getTemporalEvidence();
        this.timeStamps = evidence.getTstEvidence().toTimeStampAndCRLArray();
    }

    TimeStampDataUtil(TimeStampedDataParser timeStampedData)
        throws IOException
    {       
        this.metaDataUtil = new MetaDataUtil(timeStampedData.getMetaData());

        Evidence evidence = timeStampedData.getTemporalEvidence();
        this.timeStamps = evidence.getTstEvidence().toTimeStampAndCRLArray();
    }

    TimeStampToken getTimeStampToken(TimeStampAndCRL timeStampAndCRL)
        throws CMSException
    {
        ContentInfo timeStampToken = timeStampAndCRL.getTimeStampToken();

        try
        {
            TimeStampToken token = new TimeStampToken(timeStampToken);
            return token;
        }
        catch (IOException e)
        {
            throw new CMSException("unable to parse token data: " + e.getMessage(), e);
        }
        catch (TSPException e)
        {
            if (e.getCause() instanceof CMSException)
            {
                throw (CMSException)e.getCause();
            }

            throw new CMSException("token data invalid: " + e.getMessage(), e);
        }
        catch (IllegalArgumentException e)
        {
            throw new CMSException("token data invalid: " + e.getMessage(), e);
        }
    }

    void initialiseMessageImprintDigestCalculator(DigestCalculator calculator)
        throws CMSException
    {
        metaDataUtil.initialiseMessageImprintDigestCalculator(calculator);
    }

    DigestCalculator getMessageImprintDigestCalculator(DigestCalculatorProvider calculatorProvider)
        throws OperatorCreationException
    {
        TimeStampToken token;

        try
        {
            token = this.getTimeStampToken(timeStamps[0]);

            TimeStampTokenInfo info = token.getTimeStampInfo();
            ASN1ObjectIdentifier algOID = info.getMessageImprintAlgOID();

            DigestCalculator calc = calculatorProvider.get(new AlgorithmIdentifier(algOID));

            initialiseMessageImprintDigestCalculator(calc);

            return calc;
        }
        catch (CMSException e)
        {
            throw new OperatorCreationException("unable to extract algorithm ID: " + e.getMessage(), e);
        }
    }

    TimeStampToken[] getTimeStampTokens()
        throws CMSException
    {
        TimeStampToken[] tokens = new TimeStampToken[timeStamps.length];
        for (int i = 0; i < timeStamps.length; i++)
        {
            tokens[i] = this.getTimeStampToken(timeStamps[i]);
        }

        return tokens;
    }

    TimeStampAndCRL[] getTimeStamps()
    {
        return timeStamps;
    }

    byte[] calculateNextHash(DigestCalculator calculator)
        throws CMSException
    {
        TimeStampAndCRL tspToken = timeStamps[timeStamps.length - 1];

        OutputStream out = calculator.getOutputStream();

        try
        {
            out.write(tspToken.getEncoded(ASN1Encoding.DER));

            out.close();

            return calculator.getDigest();
        }
        catch (IOException e)
        {
            throw new CMSException("exception calculating hash: " + e.getMessage(), e);
        }
    }

    
Validate the digests present in the TimeStampTokens contained in the CMSTimeStampedData.
/** * Validate the digests present in the TimeStampTokens contained in the CMSTimeStampedData. */
void validate(DigestCalculatorProvider calculatorProvider, byte[] dataDigest) throws ImprintDigestInvalidException, CMSException { byte[] currentDigest = dataDigest; for (int i = 0; i < timeStamps.length; i++) { try { TimeStampToken token = this.getTimeStampToken(timeStamps[i]); if (i > 0) { TimeStampTokenInfo info = token.getTimeStampInfo(); DigestCalculator calculator = calculatorProvider.get(info.getHashAlgorithm()); calculator.getOutputStream().write(timeStamps[i - 1].getEncoded(ASN1Encoding.DER)); currentDigest = calculator.getDigest(); } this.compareDigest(token, currentDigest); } catch (IOException e) { throw new CMSException("exception calculating hash: " + e.getMessage(), e); } catch (OperatorCreationException e) { throw new CMSException("cannot create digest: " + e.getMessage(), e); } } } void validate(DigestCalculatorProvider calculatorProvider, byte[] dataDigest, TimeStampToken timeStampToken) throws ImprintDigestInvalidException, CMSException { byte[] currentDigest = dataDigest; byte[] encToken; try { encToken = timeStampToken.getEncoded(); } catch (IOException e) { throw new CMSException("exception encoding timeStampToken: " + e.getMessage(), e); } for (int i = 0; i < timeStamps.length; i++) { try { TimeStampToken token = this.getTimeStampToken(timeStamps[i]); if (i > 0) { TimeStampTokenInfo info = token.getTimeStampInfo(); DigestCalculator calculator = calculatorProvider.get(info.getHashAlgorithm()); calculator.getOutputStream().write(timeStamps[i - 1].getEncoded(ASN1Encoding.DER)); currentDigest = calculator.getDigest(); } this.compareDigest(token, currentDigest); if (Arrays.areEqual(token.getEncoded(), encToken)) { return; } } catch (IOException e) { throw new CMSException("exception calculating hash: " + e.getMessage(), e); } catch (OperatorCreationException e) { throw new CMSException("cannot create digest: " + e.getMessage(), e); } } throw new ImprintDigestInvalidException("passed in token not associated with timestamps present", timeStampToken); } private void compareDigest(TimeStampToken timeStampToken, byte[] digest) throws ImprintDigestInvalidException { TimeStampTokenInfo info = timeStampToken.getTimeStampInfo(); byte[] tsrMessageDigest = info.getMessageImprintDigest(); if (!Arrays.areEqual(digest, tsrMessageDigest)) { throw new ImprintDigestInvalidException("hash calculated is different from MessageImprintDigest found in TimeStampToken", timeStampToken); } } String getFileName() { return metaDataUtil.getFileName(); } String getMediaType() { return metaDataUtil.getMediaType(); } AttributeTable getOtherMetaData() { return new AttributeTable(metaDataUtil.getOtherMetaData()); } }