1package org.bouncycastle.cms;
2
3import java.io.ByteArrayOutputStream;
4import java.io.IOException;
5import java.io.OutputStream;
6import java.util.ArrayList;
7import java.util.Iterator;
8import java.util.List;
9
10import org.bouncycastle.asn1.ASN1EncodableVector;
11import org.bouncycastle.asn1.ASN1ObjectIdentifier;
12import org.bouncycastle.asn1.ASN1OctetString;
13import org.bouncycastle.asn1.ASN1Set;
14import org.bouncycastle.asn1.BEROctetString;
15import org.bouncycastle.asn1.DERSet;
16import org.bouncycastle.asn1.cms.CMSObjectIdentifiers;
17import org.bouncycastle.asn1.cms.ContentInfo;
18import org.bouncycastle.asn1.cms.SignedData;
19import org.bouncycastle.asn1.cms.SignerInfo;
20
21/**
22 * general class for generating a pkcs7-signature message.
23 * <p>
24 * A simple example of usage, generating a detached signature.
25 *
26 * <pre>
27 *      List             certList = new ArrayList();
28 *      CMSTypedData     msg = new CMSProcessableByteArray("Hello world!".getBytes());
29 *
30 *      certList.add(signCert);
31 *
32 *      Store           certs = new JcaCertStore(certList);
33 *
34 *      CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
35 *      ContentSigner sha1Signer = new JcaContentSignerBuilder("SHA1withRSA").setProvider("BC").build(signKP.getPrivate());
36 *
37 *      gen.addSignerInfoGenerator(
38 *                new JcaSignerInfoGeneratorBuilder(
39 *                     new JcaDigestCalculatorProviderBuilder().setProvider("BC").build())
40 *                     .build(sha1Signer, signCert));
41 *
42 *      gen.addCertificates(certs);
43 *
44 *      CMSSignedData sigData = gen.generate(msg, false);
45 * </pre>
46 */
47public class CMSSignedDataGenerator
48    extends CMSSignedGenerator
49{
50    private List signerInfs = new ArrayList();
51
52    /**
53     * base constructor
54     */
55    public CMSSignedDataGenerator()
56    {
57    }
58
59    /**
60     * Generate a CMS Signed Data object carrying a detached CMS signature.
61     *
62     * @param content the content to be signed.
63     */
64    public CMSSignedData generate(
65        CMSTypedData content)
66        throws CMSException
67    {
68        return generate(content, false);
69    }
70
71    /**
72     * Generate a CMS Signed Data object which can be carrying a detached CMS signature, or have encapsulated data, depending on the value
73     * of the encapsulated parameter.
74     *
75     * @param content the content to be signed.
76     * @param encapsulate true if the content should be encapsulated in the signature, false otherwise.
77     */
78    public CMSSignedData generate(
79        // FIXME Avoid accessing more than once to support CMSProcessableInputStream
80        CMSTypedData content,
81        boolean encapsulate)
82        throws CMSException
83    {
84        if (!signerInfs.isEmpty())
85        {
86            throw new IllegalStateException("this method can only be used with SignerInfoGenerator");
87        }
88
89                // TODO
90//        if (signerInfs.isEmpty())
91//        {
92//            /* RFC 3852 5.2
93//             * "In the degenerate case where there are no signers, the
94//             * EncapsulatedContentInfo value being "signed" is irrelevant.  In this
95//             * case, the content type within the EncapsulatedContentInfo value being
96//             * "signed" MUST be id-data (as defined in section 4), and the content
97//             * field of the EncapsulatedContentInfo value MUST be omitted."
98//             */
99//            if (encapsulate)
100//            {
101//                throw new IllegalArgumentException("no signers, encapsulate must be false");
102//            }
103//            if (!DATA.equals(eContentType))
104//            {
105//                throw new IllegalArgumentException("no signers, eContentType must be id-data");
106//            }
107//        }
108//
109//        if (!DATA.equals(eContentType))
110//        {
111//            /* RFC 3852 5.3
112//             * [The 'signedAttrs']...
113//             * field is optional, but it MUST be present if the content type of
114//             * the EncapsulatedContentInfo value being signed is not id-data.
115//             */
116//            // TODO signedAttrs must be present for all signers
117//        }
118
119        ASN1EncodableVector  digestAlgs = new ASN1EncodableVector();
120        ASN1EncodableVector  signerInfos = new ASN1EncodableVector();
121
122        digests.clear();  // clear the current preserved digest state
123
124        //
125        // add the precalculated SignerInfo objects.
126        //
127        for (Iterator it = _signers.iterator(); it.hasNext();)
128        {
129            SignerInformation signer = (SignerInformation)it.next();
130            digestAlgs.add(CMSSignedHelper.INSTANCE.fixAlgID(signer.getDigestAlgorithmID()));
131
132            // TODO Verify the content type and calculated digest match the precalculated SignerInfo
133            signerInfos.add(signer.toASN1Structure());
134        }
135
136        //
137        // add the SignerInfo objects
138        //
139        ASN1ObjectIdentifier contentTypeOID = content.getContentType();
140
141        ASN1OctetString octs = null;
142
143        if (content != null)
144        {
145            ByteArrayOutputStream bOut = null;
146
147            if (encapsulate)
148            {
149                bOut = new ByteArrayOutputStream();
150            }
151
152            OutputStream cOut = CMSUtils.attachSignersToOutputStream(signerGens, bOut);
153
154            // Just in case it's unencapsulated and there are no signers!
155            cOut = CMSUtils.getSafeOutputStream(cOut);
156
157            try
158            {
159                content.write(cOut);
160
161                cOut.close();
162            }
163            catch (IOException e)
164            {
165                throw new CMSException("data processing exception: " + e.getMessage(), e);
166            }
167
168            if (encapsulate)
169            {
170                octs = new BEROctetString(bOut.toByteArray());
171            }
172        }
173
174        for (Iterator it = signerGens.iterator(); it.hasNext();)
175        {
176            SignerInfoGenerator sGen = (SignerInfoGenerator)it.next();
177            SignerInfo inf = sGen.generate(contentTypeOID);
178
179            digestAlgs.add(inf.getDigestAlgorithm());
180            signerInfos.add(inf);
181
182            byte[] calcDigest = sGen.getCalculatedDigest();
183
184            if (calcDigest != null)
185            {
186                digests.put(inf.getDigestAlgorithm().getAlgorithm().getId(), calcDigest);
187            }
188        }
189
190        ASN1Set certificates = null;
191
192        if (certs.size() != 0)
193        {
194            certificates = CMSUtils.createBerSetFromList(certs);
195        }
196
197        ASN1Set certrevlist = null;
198
199        if (crls.size() != 0)
200        {
201            certrevlist = CMSUtils.createBerSetFromList(crls);
202        }
203
204        ContentInfo encInfo = new ContentInfo(contentTypeOID, octs);
205
206        SignedData  sd = new SignedData(
207                                 new DERSet(digestAlgs),
208                                 encInfo,
209                                 certificates,
210                                 certrevlist,
211                                 new DERSet(signerInfos));
212
213        ContentInfo contentInfo = new ContentInfo(
214            CMSObjectIdentifiers.signedData, sd);
215
216        return new CMSSignedData(content, contentInfo);
217    }
218
219    /**
220     * generate a set of one or more SignerInformation objects representing counter signatures on
221     * the passed in SignerInformation object.
222     *
223     * @param signer the signer to be countersigned
224     * @return a store containing the signers.
225     */
226    public SignerInformationStore generateCounterSigners(SignerInformation signer)
227        throws CMSException
228    {
229        return this.generate(new CMSProcessableByteArray(null, signer.getSignature()), false).getSignerInfos();
230    }
231}
232
233