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.
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.
A simple example of usage for an encapsulated signature.
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.
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)));
}
Note also: this class does not introduce buffering - if you are processing large files you should create
the parser with:
CMSSignedDataParser ep = new CMSSignedDataParser(new BufferedInputStream(encapSigData, bufSize));
where bufSize is a suitably large buffer size.
/**
* 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
/**
* base constructor - with encapsulated content
*/
public CMSSignedDataParser(
DigestCalculatorProvider digestCalculatorProvider,
InputStream sigData)
throws CMSException
{
this(digestCalculatorProvider, null, sigData);
}
base constructor
Params: - digestCalculatorProvider – for generating accumulating digests
- signedContent – the content that was signed.
- sigData – the signature object stream.
/**
* 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
Returns: the version number
/**
* Return the version number for the SignedData object
*
* @return the version number
*/
public int getVersion()
{
return _signedData.getVersion().getValue().intValue();
}
Return the digest algorithm identifiers for the SignedData object
Returns: the set of digest algorithm identifiers
/**
* 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 –
/**
* 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.
Returns: a Store of X509CertificateHolder objects.
/**
* 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.
Returns: a Store of X509CRLHolder objects.
/**
* 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.
Returns: a Store of X509AttributeCertificateHolder objects.
/**
* 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.
Params: - otherRevocationInfoFormat – OID of the format type been looked for.
Returns: a Store of ASN1Encodable objects representing any objects of otherRevocationInfoFormat found.
/**
* 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.
Returns: the OID for the content type.
/**
* 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.
The output stream is returned unclosed.
Params: - original – the signed data stream to be used as a base.
- signerInformationStore – the new signer information store to use.
- out – the stream to write the new signed data object to.
Returns: out.
/**
* 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.
The output stream is returned unclosed.
Params: - original – the signed data stream to be used as a base.
- certs – new certificates to be used, if any.
- crls – new CRLs to be used, if any.
- attrCerts – new attribute certificates to be used, if any.
- out – the stream to write the new signed data object to.
Throws: - CMSException – if there is an error processing the CertStore
Returns: 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());
// }
}