org.bouncycastle.cms.CMSSignedDataParser.java Source code

Java tutorial

Introduction

Here is the source code for org.bouncycastle.cms.CMSSignedDataParser.java

Source

package org.bouncycastle.cms;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.bouncycastle.asn1.ASN1Encodable;
import org.bouncycastle.asn1.ASN1EncodableVector;
import org.bouncycastle.asn1.ASN1Generator;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.ASN1OctetStringParser;
import org.bouncycastle.asn1.ASN1SequenceParser;
import org.bouncycastle.asn1.ASN1Set;
import org.bouncycastle.asn1.ASN1SetParser;
import org.bouncycastle.asn1.ASN1StreamParser;
import org.bouncycastle.asn1.BERSequenceGenerator;
import org.bouncycastle.asn1.BERSetParser;
import org.bouncycastle.asn1.BERTaggedObject;
import org.bouncycastle.asn1.BERTags;
import org.bouncycastle.asn1.DERSet;
import org.bouncycastle.asn1.DERTaggedObject;
import org.bouncycastle.asn1.cms.CMSObjectIdentifiers;
import org.bouncycastle.asn1.cms.ContentInfoParser;
import org.bouncycastle.asn1.cms.SignedDataParser;
import org.bouncycastle.asn1.cms.SignerInfo;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.operator.DigestCalculator;
import org.bouncycastle.operator.DigestCalculatorProvider;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.util.Store;
import org.bouncycastle.util.io.Streams;

/**
 * Parsing class for an CMS Signed Data object from an input stream.
 * <p>
 * Note: that because we are in a streaming mode only one signer can be tried and it is important 
 * that the methods on the parser are called in the appropriate order.
 * </p>
 * <p>
 * A simple example of usage for an encapsulated signature.
 * </p>
 * <p>
 * Two notes: first, in the example below the validity of
 * the certificate isn't verified, just the fact that one of the certs 
 * matches the given signer, and, second, because we are in a streaming
 * mode the order of the operations is important.
 * </p>
 * <pre>
 *      CMSSignedDataParser     sp = new CMSSignedDataParser(new JcaDigestCalculatorProviderBuilder().setProvider("BC").build(), encapSigData);
 *
 *      sp.getSignedContent().drain();
 *
 *      Store                   certStore = sp.getCertificates();
 *      SignerInformationStore  signers = sp.getSignerInfos();
 *      
 *      Collection              c = signers.getSigners();
 *      Iterator                it = c.iterator();
 *
 *      while (it.hasNext())
 *      {
 *          SignerInformation   signer = (SignerInformation)it.next();
 *          Collection          certCollection = certStore.getMatches(signer.getSID());
 *
 *          Iterator        certIt = certCollection.iterator();
 *          X509CertificateHolder cert = (X509CertificateHolder)certIt.next();
 *
 *          System.out.println("verify returns: " + signer.verify(new JcaSimpleSignerInfoVerifierBuilder().setProvider("BC").build(cert)));
 *      }
 * </pre>
 *  Note also: this class does not introduce buffering - if you are processing large files you should create
 *  the parser with:
 *  <pre>
 *          CMSSignedDataParser     ep = new CMSSignedDataParser(new BufferedInputStream(encapSigData, bufSize));
 *  </pre>
 *  where bufSize is a suitably large buffer size.
 */
public class CMSSignedDataParser extends CMSContentInfoParser {
    private static final CMSSignedHelper HELPER = CMSSignedHelper.INSTANCE;

    private SignedDataParser _signedData;
    private ASN1ObjectIdentifier _signedContentType;
    private CMSTypedStream _signedContent;
    private Map digests;
    private Set<AlgorithmIdentifier> digestAlgorithms;

    private SignerInformationStore _signerInfoStore;
    private ASN1Set _certSet, _crlSet;
    private boolean _isCertCrlParsed;

    public CMSSignedDataParser(DigestCalculatorProvider digestCalculatorProvider, byte[] sigBlock)
            throws CMSException {
        this(digestCalculatorProvider, new ByteArrayInputStream(sigBlock));
    }

    public CMSSignedDataParser(DigestCalculatorProvider digestCalculatorProvider, CMSTypedStream signedContent,
            byte[] sigBlock) throws CMSException {
        this(digestCalculatorProvider, signedContent, new ByteArrayInputStream(sigBlock));
    }

    /**
     * base constructor - with encapsulated content
     */
    public CMSSignedDataParser(DigestCalculatorProvider digestCalculatorProvider, InputStream sigData)
            throws CMSException {
        this(digestCalculatorProvider, null, sigData);
    }

    /**
     * base constructor
     *
     * @param digestCalculatorProvider for generating accumulating digests
     * @param signedContent the content that was signed.
     * @param sigData the signature object stream.
     */
    public CMSSignedDataParser(DigestCalculatorProvider digestCalculatorProvider, CMSTypedStream signedContent,
            InputStream sigData) throws CMSException {
        super(sigData);

        try {
            _signedContent = signedContent;
            _signedData = SignedDataParser.getInstance(_contentInfo.getContent(BERTags.SEQUENCE));
            digests = new HashMap();

            ASN1SetParser digAlgs = _signedData.getDigestAlgorithms();
            ASN1Encodable o;

            Set<AlgorithmIdentifier> algSet = new HashSet<AlgorithmIdentifier>();

            while ((o = digAlgs.readObject()) != null) {
                AlgorithmIdentifier algId = AlgorithmIdentifier.getInstance(o);

                algSet.add(algId);

                try {
                    DigestCalculator calculator = digestCalculatorProvider.get(algId);

                    if (calculator != null) {
                        this.digests.put(algId.getAlgorithm(), calculator);
                    }
                } catch (OperatorCreationException e) {
                    //  ignore
                }
            }

            digestAlgorithms = Collections.unmodifiableSet(algSet);

            //
            // If the message is simply a certificate chain message getContent() may return null.
            //
            ContentInfoParser cont = _signedData.getEncapContentInfo();
            ASN1Encodable contentParser = cont.getContent(BERTags.OCTET_STRING);

            if (contentParser instanceof ASN1OctetStringParser) {
                ASN1OctetStringParser octs = (ASN1OctetStringParser) contentParser;

                CMSTypedStream ctStr = new CMSTypedStream(cont.getContentType(), octs.getOctetStream());

                if (_signedContent == null) {
                    _signedContent = ctStr;
                } else {
                    //
                    // content passed in, need to read past empty encapsulated content info object if present
                    //
                    ctStr.drain();
                }
            } else if (contentParser != null) {
                PKCS7TypedStream pkcs7Stream = new PKCS7TypedStream(cont.getContentType(), contentParser);

                if (_signedContent == null) {
                    _signedContent = pkcs7Stream;
                } else {
                    //
                    // content passed in, need to read past empty encapsulated content info object if present
                    //
                    pkcs7Stream.drain();
                }
            }

            if (signedContent == null) {
                _signedContentType = cont.getContentType();
            } else {
                _signedContentType = _signedContent.getContentType();
            }
        } catch (IOException e) {
            throw new CMSException("io exception: " + e.getMessage(), e);
        }
    }

    /**
     * Return the version number for the SignedData object
     *
     * @return the version number
     */
    public int getVersion() {
        return _signedData.getVersion().intValueExact();
    }

    /**
     * Return the digest algorithm identifiers for the SignedData object
     *
     * @return the set of digest algorithm identifiers
     */
    public Set<AlgorithmIdentifier> getDigestAlgorithmIDs() {
        return digestAlgorithms;
    }

    /**
     * return the collection of signers that are associated with the
     * signatures for the message.
     * @throws CMSException 
     */
    public SignerInformationStore getSignerInfos() throws CMSException {
        if (_signerInfoStore == null) {
            populateCertCrlSets();

            List signerInfos = new ArrayList();
            Map hashes = new HashMap();

            Iterator it = digests.keySet().iterator();
            while (it.hasNext()) {
                Object digestKey = it.next();

                hashes.put(digestKey, ((DigestCalculator) digests.get(digestKey)).getDigest());
            }

            try {
                ASN1SetParser s = _signedData.getSignerInfos();
                ASN1Encodable o;

                while ((o = s.readObject()) != null) {
                    SignerInfo info = SignerInfo.getInstance(o.toASN1Primitive());

                    byte[] hash = (byte[]) hashes.get(info.getDigestAlgorithm().getAlgorithm());

                    signerInfos.add(new SignerInformation(info, _signedContentType, null, hash));
                }
            } catch (IOException e) {
                throw new CMSException("io exception: " + e.getMessage(), e);
            }

            _signerInfoStore = new SignerInformationStore(signerInfos);
        }

        return _signerInfoStore;
    }

    /**
     * Return any X.509 certificate objects in this SignedData structure as a Store of X509CertificateHolder objects.
     *
     * @return a Store of X509CertificateHolder objects.
     */
    public Store getCertificates() throws CMSException {
        populateCertCrlSets();

        return HELPER.getCertificates(_certSet);
    }

    /**
     * Return any X.509 CRL objects in this SignedData structure as a Store of X509CRLHolder objects.
     *
     * @return a Store of X509CRLHolder objects.
     */
    public Store getCRLs() throws CMSException {
        populateCertCrlSets();

        return HELPER.getCRLs(_crlSet);
    }

    /**
     * Return any X.509 attribute certificate objects in this SignedData structure as a Store of X509AttributeCertificateHolder objects.
     *
     * @return a Store of X509AttributeCertificateHolder objects.
     */
    public Store getAttributeCertificates() throws CMSException {
        populateCertCrlSets();

        return HELPER.getAttributeCertificates(_certSet);
    }

    /**
     * Return any OtherRevocationInfo OtherRevInfo objects of the type indicated by otherRevocationInfoFormat in
     * this SignedData structure.
     *
     * @param otherRevocationInfoFormat OID of the format type been looked for.
     *
     * @return a Store of ASN1Encodable objects representing any objects of otherRevocationInfoFormat found.
     */
    public Store getOtherRevocationInfo(ASN1ObjectIdentifier otherRevocationInfoFormat) throws CMSException {
        populateCertCrlSets();

        return HELPER.getOtherRevocationInfo(otherRevocationInfoFormat, _crlSet);
    }

    private void populateCertCrlSets() throws CMSException {
        if (_isCertCrlParsed) {
            return;
        }

        _isCertCrlParsed = true;

        try {
            // care! Streaming - these must be done in exactly this order.
            _certSet = getASN1Set(_signedData.getCertificates());
            _crlSet = getASN1Set(_signedData.getCrls());
        } catch (IOException e) {
            throw new CMSException("problem parsing cert/crl sets", e);
        }
    }

    /**
     * Return the a string representation of the OID associated with the
     * encapsulated content info structure carried in the signed data.
     * 
     * @return the OID for the content type.
     */
    public String getSignedContentTypeOID() {
        return _signedContentType.getId();
    }

    public CMSTypedStream getSignedContent() {
        if (_signedContent == null) {
            return null;
        }

        InputStream digStream = CMSUtils.attachDigestsToInputStream(digests.values(),
                _signedContent.getContentStream());

        return new CMSTypedStream(_signedContent.getContentType(), digStream);
    }

    /**
     * Replace the signerinformation store associated with the passed
     * in message contained in the stream original with the new one passed in.
     * You would probably only want to do this if you wanted to change the unsigned
     * attributes associated with a signer, or perhaps delete one.
     * <p>
     * The output stream is returned unclosed.
     * </p>
     * @param original the signed data stream to be used as a base.
     * @param signerInformationStore the new signer information store to use.
     * @param out the stream to write the new signed data object to.
     * @return out.
     */
    public static OutputStream replaceSigners(InputStream original, SignerInformationStore signerInformationStore,
            OutputStream out) throws CMSException, IOException {
        ASN1StreamParser in = new ASN1StreamParser(original);
        ContentInfoParser contentInfo = new ContentInfoParser((ASN1SequenceParser) in.readObject());
        SignedDataParser signedData = SignedDataParser.getInstance(contentInfo.getContent(BERTags.SEQUENCE));

        BERSequenceGenerator sGen = new BERSequenceGenerator(out);

        sGen.addObject(CMSObjectIdentifiers.signedData);

        BERSequenceGenerator sigGen = new BERSequenceGenerator(sGen.getRawOutputStream(), 0, true);

        // version number
        sigGen.addObject(signedData.getVersion());

        // digests
        signedData.getDigestAlgorithms().toASN1Primitive(); // skip old ones

        ASN1EncodableVector digestAlgs = new ASN1EncodableVector();

        for (Iterator it = signerInformationStore.getSigners().iterator(); it.hasNext();) {
            SignerInformation signer = (SignerInformation) it.next();
            digestAlgs.add(CMSSignedHelper.INSTANCE.fixAlgID(signer.getDigestAlgorithmID()));
        }

        sigGen.getRawOutputStream().write(new DERSet(digestAlgs).getEncoded());

        // encap content info
        ContentInfoParser encapContentInfo = signedData.getEncapContentInfo();

        BERSequenceGenerator eiGen = new BERSequenceGenerator(sigGen.getRawOutputStream());

        eiGen.addObject(encapContentInfo.getContentType());

        pipeEncapsulatedOctetString(encapContentInfo, eiGen.getRawOutputStream());

        eiGen.close();

        writeSetToGeneratorTagged(sigGen, signedData.getCertificates(), 0);
        writeSetToGeneratorTagged(sigGen, signedData.getCrls(), 1);

        ASN1EncodableVector signerInfos = new ASN1EncodableVector();
        for (Iterator it = signerInformationStore.getSigners().iterator(); it.hasNext();) {
            SignerInformation signer = (SignerInformation) it.next();

            signerInfos.add(signer.toASN1Structure());
        }

        sigGen.getRawOutputStream().write(new DERSet(signerInfos).getEncoded());

        sigGen.close();

        sGen.close();

        return out;
    }

    /**
     * Replace the certificate and CRL information associated with this
     * CMSSignedData object with the new one passed in.
     * <p>
     * The output stream is returned unclosed.
     * </p>
     * @param original the signed data stream to be used as a base.
     * @param certs new certificates to be used, if any.
     * @param crls new CRLs to be used, if any.
     * @param attrCerts new attribute certificates to be used, if any.
     * @param out the stream to write the new signed data object to.
     * @return out.
     * @exception CMSException if there is an error processing the CertStore
     */
    public static OutputStream replaceCertificatesAndCRLs(InputStream original, Store certs, Store crls,
            Store attrCerts, OutputStream out) throws CMSException, IOException {
        ASN1StreamParser in = new ASN1StreamParser(original);
        ContentInfoParser contentInfo = new ContentInfoParser((ASN1SequenceParser) in.readObject());
        SignedDataParser signedData = SignedDataParser.getInstance(contentInfo.getContent(BERTags.SEQUENCE));

        BERSequenceGenerator sGen = new BERSequenceGenerator(out);

        sGen.addObject(CMSObjectIdentifiers.signedData);

        BERSequenceGenerator sigGen = new BERSequenceGenerator(sGen.getRawOutputStream(), 0, true);

        // version number
        sigGen.addObject(signedData.getVersion());

        // digests
        sigGen.getRawOutputStream().write(signedData.getDigestAlgorithms().toASN1Primitive().getEncoded());

        // encap content info
        ContentInfoParser encapContentInfo = signedData.getEncapContentInfo();

        BERSequenceGenerator eiGen = new BERSequenceGenerator(sigGen.getRawOutputStream());

        eiGen.addObject(encapContentInfo.getContentType());

        pipeEncapsulatedOctetString(encapContentInfo, eiGen.getRawOutputStream());

        eiGen.close();

        //
        // skip existing certs and CRLs
        //
        getASN1Set(signedData.getCertificates());
        getASN1Set(signedData.getCrls());

        //
        // replace the certs and crls in the SignedData object
        //
        if (certs != null || attrCerts != null) {
            List certificates = new ArrayList();

            if (certs != null) {
                certificates.addAll(CMSUtils.getCertificatesFromStore(certs));
            }
            if (attrCerts != null) {
                certificates.addAll(CMSUtils.getAttributeCertificatesFromStore(attrCerts));
            }

            ASN1Set asn1Certs = CMSUtils.createBerSetFromList(certificates);

            if (asn1Certs.size() > 0) {
                sigGen.getRawOutputStream().write(new DERTaggedObject(false, 0, asn1Certs).getEncoded());
            }
        }

        if (crls != null) {
            ASN1Set asn1Crls = CMSUtils.createBerSetFromList(CMSUtils.getCRLsFromStore(crls));

            if (asn1Crls.size() > 0) {
                sigGen.getRawOutputStream().write(new DERTaggedObject(false, 1, asn1Crls).getEncoded());
            }
        }

        sigGen.getRawOutputStream().write(signedData.getSignerInfos().toASN1Primitive().getEncoded());

        sigGen.close();

        sGen.close();

        return out;
    }

    private static void writeSetToGeneratorTagged(ASN1Generator asn1Gen, ASN1SetParser asn1SetParser, int tagNo)
            throws IOException {
        ASN1Set asn1Set = getASN1Set(asn1SetParser);

        if (asn1Set != null) {
            if (asn1SetParser instanceof BERSetParser) {
                asn1Gen.getRawOutputStream().write(new BERTaggedObject(false, tagNo, asn1Set).getEncoded());
            } else {
                asn1Gen.getRawOutputStream().write(new DERTaggedObject(false, tagNo, asn1Set).getEncoded());
            }
        }
    }

    private static ASN1Set getASN1Set(ASN1SetParser asn1SetParser) {
        return asn1SetParser == null ? null : ASN1Set.getInstance(asn1SetParser.toASN1Primitive());
    }

    private static void pipeEncapsulatedOctetString(ContentInfoParser encapContentInfo,
            OutputStream rawOutputStream) throws IOException {
        ASN1OctetStringParser octs = (ASN1OctetStringParser) encapContentInfo.getContent(BERTags.OCTET_STRING);

        if (octs != null) {
            pipeOctetString(octs, rawOutputStream);
        }

        //        BERTaggedObjectParser contentObject = (BERTaggedObjectParser)encapContentInfo.getContentObject();
        //        if (contentObject != null)
        //        {
        //            // Handle IndefiniteLengthInputStream safely
        //            InputStream input = ASN1StreamParser.getSafeRawInputStream(contentObject.getContentStream(true));
        //
        //            // TODO BerTaggedObjectGenerator?
        //            BEROutputStream berOut = new BEROutputStream(rawOutputStream);
        //            berOut.write(DERTags.CONSTRUCTED | DERTags.TAGGED | 0);
        //            berOut.write(0x80);
        //
        //            pipeRawOctetString(input, rawOutputStream);
        //
        //            berOut.write(0x00);
        //            berOut.write(0x00);
        //
        //            input.close();
        //        }
    }

    private static void pipeOctetString(ASN1OctetStringParser octs, OutputStream output) throws IOException {
        // TODO Allow specification of a specific fragment size?
        OutputStream outOctets = CMSUtils.createBEROctetOutputStream(output, 0, true, 0);
        Streams.pipeAll(octs.getOctetStream(), outOctets);
        outOctets.close();
    }

    //    private static void pipeRawOctetString(
    //        InputStream     rawInput,
    //        OutputStream    rawOutput)
    //        throws IOException
    //    {
    //        InputStream tee = new TeeInputStream(rawInput, rawOutput);
    //        ASN1StreamParser sp = new ASN1StreamParser(tee);
    //        ASN1OctetStringParser octs = (ASN1OctetStringParser)sp.readObject();
    //        Streams.drain(octs.getOctetStream());
    //    }
}