1 package org.bouncycastle.cms;
2 
3 import java.io.ByteArrayOutputStream;
4 import java.io.IOException;
5 import java.io.OutputStream;
6 import java.util.ArrayList;
7 import java.util.Iterator;
8 import java.util.List;
9 
10 import org.bouncycastle.asn1.ASN1EncodableVector;
11 import org.bouncycastle.asn1.ASN1ObjectIdentifier;
12 import org.bouncycastle.asn1.ASN1OctetString;
13 import org.bouncycastle.asn1.ASN1Set;
14 import org.bouncycastle.asn1.BEROctetString;
15 import org.bouncycastle.asn1.DERSet;
16 import org.bouncycastle.asn1.cms.CMSObjectIdentifiers;
17 import org.bouncycastle.asn1.cms.ContentInfo;
18 import org.bouncycastle.asn1.cms.SignedData;
19 import 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  */
47 public class CMSSignedDataGenerator
48     extends CMSSignedGenerator
49 {
50     private List signerInfs = new ArrayList();
51 
52     /**
53      * base constructor
54      */
CMSSignedDataGenerator()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      */
generate( CMSTypedData content)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      */
generate( CMSTypedData content, boolean encapsulate)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.getContent() != 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      */
generateCounterSigners(SignerInformation signer)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