blob: 5417ce4d78b310cc38843b326493fe067d21698a [file] [log] [blame]
package org.bouncycastle.cms;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.bouncycastle.asn1.ASN1EncodableVector;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.ASN1OctetString;
import org.bouncycastle.asn1.ASN1Set;
import org.bouncycastle.asn1.BEROctetString;
import org.bouncycastle.asn1.DERSet;
import org.bouncycastle.asn1.cms.CMSObjectIdentifiers;
import org.bouncycastle.asn1.cms.ContentInfo;
import org.bouncycastle.asn1.cms.SignedData;
import org.bouncycastle.asn1.cms.SignerInfo;
/**
* general class for generating a pkcs7-signature message.
* <p>
* A simple example of usage, generating a detached signature.
*
* <pre>
* List certList = new ArrayList();
* CMSTypedData msg = new CMSProcessableByteArray("Hello world!".getBytes());
*
* certList.add(signCert);
*
* Store certs = new JcaCertStore(certList);
*
* CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
* ContentSigner sha1Signer = new JcaContentSignerBuilder("SHA1withRSA").setProvider("BC").build(signKP.getPrivate());
*
* gen.addSignerInfoGenerator(
* new JcaSignerInfoGeneratorBuilder(
* new JcaDigestCalculatorProviderBuilder().setProvider("BC").build())
* .build(sha1Signer, signCert));
*
* gen.addCertificates(certs);
*
* CMSSignedData sigData = gen.generate(msg, false);
* </pre>
*/
public class CMSSignedDataGenerator
extends CMSSignedGenerator
{
private List signerInfs = new ArrayList();
/**
* base constructor
*/
public CMSSignedDataGenerator()
{
}
/**
* Generate a CMS Signed Data object carrying a detached CMS signature.
*
* @param content the content to be signed.
*/
public CMSSignedData generate(
CMSTypedData content)
throws CMSException
{
return generate(content, false);
}
/**
* Generate a CMS Signed Data object which can be carrying a detached CMS signature, or have encapsulated data, depending on the value
* of the encapsulated parameter.
*
* @param content the content to be signed.
* @param encapsulate true if the content should be encapsulated in the signature, false otherwise.
*/
public CMSSignedData generate(
// FIXME Avoid accessing more than once to support CMSProcessableInputStream
CMSTypedData content,
boolean encapsulate)
throws CMSException
{
if (!signerInfs.isEmpty())
{
throw new IllegalStateException("this method can only be used with SignerInfoGenerator");
}
// TODO
// if (signerInfs.isEmpty())
// {
// /* RFC 3852 5.2
// * "In the degenerate case where there are no signers, the
// * EncapsulatedContentInfo value being "signed" is irrelevant. In this
// * case, the content type within the EncapsulatedContentInfo value being
// * "signed" MUST be id-data (as defined in section 4), and the content
// * field of the EncapsulatedContentInfo value MUST be omitted."
// */
// if (encapsulate)
// {
// throw new IllegalArgumentException("no signers, encapsulate must be false");
// }
// if (!DATA.equals(eContentType))
// {
// throw new IllegalArgumentException("no signers, eContentType must be id-data");
// }
// }
//
// if (!DATA.equals(eContentType))
// {
// /* RFC 3852 5.3
// * [The 'signedAttrs']...
// * field is optional, but it MUST be present if the content type of
// * the EncapsulatedContentInfo value being signed is not id-data.
// */
// // TODO signedAttrs must be present for all signers
// }
ASN1EncodableVector digestAlgs = new ASN1EncodableVector();
ASN1EncodableVector signerInfos = new ASN1EncodableVector();
digests.clear(); // clear the current preserved digest state
//
// add the precalculated SignerInfo objects.
//
for (Iterator it = _signers.iterator(); it.hasNext();)
{
SignerInformation signer = (SignerInformation)it.next();
digestAlgs.add(CMSSignedHelper.INSTANCE.fixAlgID(signer.getDigestAlgorithmID()));
// TODO Verify the content type and calculated digest match the precalculated SignerInfo
signerInfos.add(signer.toASN1Structure());
}
//
// add the SignerInfo objects
//
ASN1ObjectIdentifier contentTypeOID = content.getContentType();
ASN1OctetString octs = null;
if (content.getContent() != null)
{
ByteArrayOutputStream bOut = null;
if (encapsulate)
{
bOut = new ByteArrayOutputStream();
}
OutputStream cOut = CMSUtils.attachSignersToOutputStream(signerGens, bOut);
// Just in case it's unencapsulated and there are no signers!
cOut = CMSUtils.getSafeOutputStream(cOut);
try
{
content.write(cOut);
cOut.close();
}
catch (IOException e)
{
throw new CMSException("data processing exception: " + e.getMessage(), e);
}
if (encapsulate)
{
octs = new BEROctetString(bOut.toByteArray());
}
}
for (Iterator it = signerGens.iterator(); it.hasNext();)
{
SignerInfoGenerator sGen = (SignerInfoGenerator)it.next();
SignerInfo inf = sGen.generate(contentTypeOID);
digestAlgs.add(inf.getDigestAlgorithm());
signerInfos.add(inf);
byte[] calcDigest = sGen.getCalculatedDigest();
if (calcDigest != null)
{
digests.put(inf.getDigestAlgorithm().getAlgorithm().getId(), calcDigest);
}
}
ASN1Set certificates = null;
if (certs.size() != 0)
{
certificates = CMSUtils.createBerSetFromList(certs);
}
ASN1Set certrevlist = null;
if (crls.size() != 0)
{
certrevlist = CMSUtils.createBerSetFromList(crls);
}
ContentInfo encInfo = new ContentInfo(contentTypeOID, octs);
SignedData sd = new SignedData(
new DERSet(digestAlgs),
encInfo,
certificates,
certrevlist,
new DERSet(signerInfos));
ContentInfo contentInfo = new ContentInfo(
CMSObjectIdentifiers.signedData, sd);
return new CMSSignedData(content, contentInfo);
}
/**
* generate a set of one or more SignerInformation objects representing counter signatures on
* the passed in SignerInformation object.
*
* @param signer the signer to be countersigned
* @return a store containing the signers.
*/
public SignerInformationStore generateCounterSigners(SignerInformation signer)
throws CMSException
{
return this.generate(new CMSProcessableByteArray(null, signer.getSignature()), false).getSignerInfos();
}
}