1package org.bouncycastle.cms;
2
3import java.io.IOException;
4import java.io.InputStream;
5import java.io.OutputStream;
6import java.util.ArrayList;
7import java.util.Collection;
8import java.util.Iterator;
9import java.util.List;
10import java.util.Map;
11
12import org.bouncycastle.asn1.ASN1EncodableVector;
13import org.bouncycastle.asn1.ASN1InputStream;
14import org.bouncycastle.asn1.ASN1ObjectIdentifier;
15import org.bouncycastle.asn1.ASN1OctetString;
16import org.bouncycastle.asn1.ASN1Sequence;
17import org.bouncycastle.asn1.ASN1Set;
18import org.bouncycastle.asn1.BERSequence;
19import org.bouncycastle.asn1.DERSet;
20import org.bouncycastle.asn1.cms.ContentInfo;
21import org.bouncycastle.asn1.cms.SignedData;
22import org.bouncycastle.asn1.cms.SignerInfo;
23import org.bouncycastle.operator.DefaultSignatureAlgorithmIdentifierFinder;
24import org.bouncycastle.operator.OperatorCreationException;
25import org.bouncycastle.operator.SignatureAlgorithmIdentifierFinder;
26import org.bouncycastle.util.Store;
27
28/**
29 * general class for handling a pkcs7-signature message.
30 *
31 * A simple example of usage - note, in the example below the validity of
32 * the certificate isn't verified, just the fact that one of the certs
33 * matches the given signer...
34 *
35 * <pre>
36 *  Store                   certStore = s.getCertificates();
37 *  SignerInformationStore  signers = s.getSignerInfos();
38 *  Collection              c = signers.getSigners();
39 *  Iterator                it = c.iterator();
40 *
41 *  while (it.hasNext())
42 *  {
43 *      SignerInformation   signer = (SignerInformation)it.next();
44 *      Collection          certCollection = certStore.getMatches(signer.getSID());
45 *
46 *      Iterator              certIt = certCollection.iterator();
47 *      X509CertificateHolder cert = (X509CertificateHolder)certIt.next();
48 *
49 *      if (signer.verify(new JcaSimpleSignerInfoVerifierBuilder().setProvider("BC").build(cert)))
50 *      {
51 *          verified++;
52 *      }
53 *  }
54 * </pre>
55 */
56public class CMSSignedData
57{
58    private static final CMSSignedHelper HELPER = CMSSignedHelper.INSTANCE;
59
60    SignedData              signedData;
61    ContentInfo             contentInfo;
62    CMSTypedData            signedContent;
63    SignerInformationStore  signerInfoStore;
64
65    private Map             hashes;
66
67    private CMSSignedData(
68        CMSSignedData   c)
69    {
70        this.signedData = c.signedData;
71        this.contentInfo = c.contentInfo;
72        this.signedContent = c.signedContent;
73        this.signerInfoStore = c.signerInfoStore;
74    }
75
76    public CMSSignedData(
77        byte[]      sigBlock)
78        throws CMSException
79    {
80        this(CMSUtils.readContentInfo(sigBlock));
81    }
82
83    public CMSSignedData(
84        CMSProcessable  signedContent,
85        byte[]          sigBlock)
86        throws CMSException
87    {
88        this(signedContent, CMSUtils.readContentInfo(sigBlock));
89    }
90
91    /**
92     * Content with detached signature, digests precomputed
93     *
94     * @param hashes a map of precomputed digests for content indexed by name of hash.
95     * @param sigBlock the signature object.
96     */
97    public CMSSignedData(
98        Map     hashes,
99        byte[]  sigBlock)
100        throws CMSException
101    {
102        this(hashes, CMSUtils.readContentInfo(sigBlock));
103    }
104
105    /**
106     * base constructor - content with detached signature.
107     *
108     * @param signedContent the content that was signed.
109     * @param sigData the signature object.
110     */
111    public CMSSignedData(
112        CMSProcessable  signedContent,
113        InputStream     sigData)
114        throws CMSException
115    {
116        this(signedContent, CMSUtils.readContentInfo(new ASN1InputStream(sigData)));
117    }
118
119    /**
120     * base constructor - with encapsulated content
121     */
122    public CMSSignedData(
123        InputStream sigData)
124        throws CMSException
125    {
126        this(CMSUtils.readContentInfo(sigData));
127    }
128
129    public CMSSignedData(
130        final CMSProcessable  signedContent,
131        ContentInfo     sigData)
132        throws CMSException
133    {
134        if (signedContent instanceof CMSTypedData)
135        {
136            this.signedContent = (CMSTypedData)signedContent;
137        }
138        else
139        {
140            this.signedContent = new CMSTypedData()
141            {
142                public ASN1ObjectIdentifier getContentType()
143                {
144                    return signedData.getEncapContentInfo().getContentType();
145                }
146
147                public void write(OutputStream out)
148                    throws IOException, CMSException
149                {
150                    signedContent.write(out);
151                }
152
153                public Object getContent()
154                {
155                    return signedContent.getContent();
156                }
157            };
158        }
159
160        this.contentInfo = sigData;
161        this.signedData = getSignedData();
162    }
163
164    public CMSSignedData(
165        Map             hashes,
166        ContentInfo     sigData)
167        throws CMSException
168    {
169        this.hashes = hashes;
170        this.contentInfo = sigData;
171        this.signedData = getSignedData();
172    }
173
174    public CMSSignedData(
175        ContentInfo sigData)
176        throws CMSException
177    {
178        this.contentInfo = sigData;
179        this.signedData = getSignedData();
180
181        //
182        // this can happen if the signed message is sent simply to send a
183        // certificate chain.
184        //
185        if (signedData.getEncapContentInfo().getContent() != null)
186        {
187            this.signedContent = new CMSProcessableByteArray(signedData.getEncapContentInfo().getContentType(),
188                    ((ASN1OctetString)(signedData.getEncapContentInfo()
189                                                .getContent())).getOctets());
190        }
191        else
192        {
193            this.signedContent = null;
194        }
195    }
196
197    private SignedData getSignedData()
198        throws CMSException
199    {
200        try
201        {
202            return SignedData.getInstance(contentInfo.getContent());
203        }
204        catch (ClassCastException e)
205        {
206            throw new CMSException("Malformed content.", e);
207        }
208        catch (IllegalArgumentException e)
209        {
210            throw new CMSException("Malformed content.", e);
211        }
212    }
213
214    /**
215     * Return the version number for this object
216     */
217    public int getVersion()
218    {
219        return signedData.getVersion().getValue().intValue();
220    }
221
222    /**
223     * return the collection of signers that are associated with the
224     * signatures for the message.
225     */
226    public SignerInformationStore getSignerInfos()
227    {
228        if (signerInfoStore == null)
229        {
230            ASN1Set         s = signedData.getSignerInfos();
231            List            signerInfos = new ArrayList();
232            SignatureAlgorithmIdentifierFinder sigAlgFinder = new DefaultSignatureAlgorithmIdentifierFinder();
233
234            for (int i = 0; i != s.size(); i++)
235            {
236                SignerInfo info = SignerInfo.getInstance(s.getObjectAt(i));
237                ASN1ObjectIdentifier contentType = signedData.getEncapContentInfo().getContentType();
238
239                if (hashes == null)
240                {
241                    signerInfos.add(new SignerInformation(info, contentType, signedContent, null));
242                }
243                else
244                {
245                    Object obj = hashes.keySet().iterator().next();
246                    byte[] hash = (obj instanceof String) ? (byte[])hashes.get(info.getDigestAlgorithm().getAlgorithm().getId()) : (byte[])hashes.get(info.getDigestAlgorithm().getAlgorithm());
247
248                    signerInfos.add(new SignerInformation(info, contentType, null, hash));
249                }
250            }
251
252            signerInfoStore = new SignerInformationStore(signerInfos);
253        }
254
255        return signerInfoStore;
256    }
257
258    /**
259     * Return any X.509 certificate objects in this SignedData structure as a Store of X509CertificateHolder objects.
260     *
261     * @return a Store of X509CertificateHolder objects.
262     */
263    public Store getCertificates()
264    {
265        return HELPER.getCertificates(signedData.getCertificates());
266    }
267
268    /**
269     * Return any X.509 CRL objects in this SignedData structure as a Store of X509CRLHolder objects.
270     *
271     * @return a Store of X509CRLHolder objects.
272     */
273    public Store getCRLs()
274    {
275        return HELPER.getCRLs(signedData.getCRLs());
276    }
277
278    /**
279     * Return any X.509 attribute certificate objects in this SignedData structure as a Store of X509AttributeCertificateHolder objects.
280     *
281     * @return a Store of X509AttributeCertificateHolder objects.
282     */
283    public Store getAttributeCertificates()
284    {
285        return HELPER.getAttributeCertificates(signedData.getCertificates());
286    }
287
288    // BEGIN android-removed
289    // /**
290    //  * Return any OtherRevocationInfo OtherRevInfo objects of the type indicated by otherRevocationInfoFormat in
291    //  * this SignedData structure.
292    //  *
293    //  * @param otherRevocationInfoFormat OID of the format type been looked for.
294    //  *
295    //  * @return a Store of ASN1Encodable objects representing any objects of otherRevocationInfoFormat found.
296    //  */
297    // public Store getOtherRevocationInfo(ASN1ObjectIdentifier otherRevocationInfoFormat)
298    // {
299    //     return HELPER.getOtherRevocationInfo(otherRevocationInfoFormat, signedData.getCRLs());
300    // }
301    // END android-removed
302
303    /**
304     * Return the a string representation of the OID associated with the
305     * encapsulated content info structure carried in the signed data.
306     *
307     * @return the OID for the content type.
308     */
309    public String getSignedContentTypeOID()
310    {
311        return signedData.getEncapContentInfo().getContentType().getId();
312    }
313
314    public CMSTypedData getSignedContent()
315    {
316        return signedContent;
317    }
318
319    /**
320     * return the ContentInfo
321     */
322    public ContentInfo toASN1Structure()
323    {
324        return contentInfo;
325    }
326
327    /**
328     * return the ASN.1 encoded representation of this object.
329     */
330    public byte[] getEncoded()
331        throws IOException
332    {
333        return contentInfo.getEncoded();
334    }
335
336    // BEGIN android-removed
337    // /**
338    //  * Verify all the SignerInformation objects and their associated counter signatures attached
339    //  * to this CMS SignedData object.
340    //  *
341    //  * @param verifierProvider  a provider of SignerInformationVerifier objects.
342    //  * @return true if all verify, false otherwise.
343    //  * @throws CMSException  if an exception occurs during the verification process.
344    //  */
345    // public boolean verifySignatures(SignerInformationVerifierProvider verifierProvider)
346    //     throws CMSException
347    // {
348    //     return verifySignatures(verifierProvider, false);
349    // }
350    //
351    // /**
352    //  * Verify all the SignerInformation objects and optionally their associated counter signatures attached
353    //  * to this CMS SignedData object.
354    //  *
355    //  * @param verifierProvider  a provider of SignerInformationVerifier objects.
356    //  * @param ignoreCounterSignatures if true don't check counter signatures. If false check counter signatures as well.
357    //  * @return true if all verify, false otherwise.
358    //  * @throws CMSException  if an exception occurs during the verification process.
359    //  */
360    // public boolean verifySignatures(SignerInformationVerifierProvider verifierProvider, boolean ignoreCounterSignatures)
361    //     throws CMSException
362    // {
363    //     Collection signers = this.getSignerInfos().getSigners();
364    //
365    //     for (Iterator it = signers.iterator(); it.hasNext();)
366    //     {
367    //         SignerInformation signer = (SignerInformation)it.next();
368    //
369    //         try
370    //         {
371    //             SignerInformationVerifier verifier = verifierProvider.get(signer.getSID());
372    //
373    //             if (!signer.verify(verifier))
374    //             {
375    //                 return false;
376    //             }
377    //
378    //             if (!ignoreCounterSignatures)
379    //             {
380    //                 Collection counterSigners = signer.getCounterSignatures().getSigners();
381    //
382    //                 for  (Iterator cIt = counterSigners.iterator(); cIt.hasNext();)
383    //                 {
384    //                     SignerInformation counterSigner = (SignerInformation)cIt.next();
385    //                     SignerInformationVerifier counterVerifier = verifierProvider.get(signer.getSID());
386    //
387    //                     if (!counterSigner.verify(counterVerifier))
388    //                     {
389    //                         return false;
390    //                     }
391    //                 }
392    //             }
393    //         }
394    //         catch (OperatorCreationException e)
395    //         {
396    //             throw new CMSException("failure in verifier provider: " + e.getMessage(), e);
397    //         }
398    //     }
399    //
400    //     return true;
401    // }
402    // END android-removed
403
404    /**
405     * Replace the SignerInformation store associated with this
406     * CMSSignedData object with the new one passed in. You would
407     * probably only want to do this if you wanted to change the unsigned
408     * attributes associated with a signer, or perhaps delete one.
409     *
410     * @param signedData the signed data object to be used as a base.
411     * @param signerInformationStore the new signer information store to use.
412     * @return a new signed data object.
413     */
414    public static CMSSignedData replaceSigners(
415        CMSSignedData           signedData,
416        SignerInformationStore  signerInformationStore)
417    {
418        //
419        // copy
420        //
421        CMSSignedData   cms = new CMSSignedData(signedData);
422
423        //
424        // replace the store
425        //
426        cms.signerInfoStore = signerInformationStore;
427
428        //
429        // replace the signers in the SignedData object
430        //
431        ASN1EncodableVector digestAlgs = new ASN1EncodableVector();
432        ASN1EncodableVector vec = new ASN1EncodableVector();
433
434        Iterator    it = signerInformationStore.getSigners().iterator();
435        while (it.hasNext())
436        {
437            SignerInformation signer = (SignerInformation)it.next();
438            digestAlgs.add(CMSSignedHelper.INSTANCE.fixAlgID(signer.getDigestAlgorithmID()));
439            vec.add(signer.toASN1Structure());
440        }
441
442        ASN1Set             digests = new DERSet(digestAlgs);
443        ASN1Set             signers = new DERSet(vec);
444        ASN1Sequence        sD = (ASN1Sequence)signedData.signedData.toASN1Primitive();
445
446        vec = new ASN1EncodableVector();
447
448        //
449        // signers are the last item in the sequence.
450        //
451        vec.add(sD.getObjectAt(0)); // version
452        vec.add(digests);
453
454        for (int i = 2; i != sD.size() - 1; i++)
455        {
456            vec.add(sD.getObjectAt(i));
457        }
458
459        vec.add(signers);
460
461        cms.signedData = SignedData.getInstance(new BERSequence(vec));
462
463        //
464        // replace the contentInfo with the new one
465        //
466        cms.contentInfo = new ContentInfo(cms.contentInfo.getContentType(), cms.signedData);
467
468        return cms;
469    }
470
471    /**
472     * Replace the certificate and CRL information associated with this
473     * CMSSignedData object with the new one passed in.
474     *
475     * @param signedData the signed data object to be used as a base.
476     * @param certificates the new certificates to be used.
477     * @param attrCerts the new attribute certificates to be used.
478     * @param crls the new CRLs to be used.
479     * @return a new signed data object.
480     * @exception CMSException if there is an error processing the CertStore
481     */
482    public static CMSSignedData replaceCertificatesAndCRLs(
483        CMSSignedData   signedData,
484        Store           certificates,
485        Store           attrCerts,
486        Store           crls)
487        throws CMSException
488    {
489        //
490        // copy
491        //
492        CMSSignedData   cms = new CMSSignedData(signedData);
493
494        //
495        // replace the certs and crls in the SignedData object
496        //
497        ASN1Set certSet = null;
498        ASN1Set crlSet = null;
499
500        if (certificates != null || attrCerts != null)
501        {
502            List certs = new ArrayList();
503
504            if (certificates != null)
505            {
506                certs.addAll(CMSUtils.getCertificatesFromStore(certificates));
507            }
508            if (attrCerts != null)
509            {
510                certs.addAll(CMSUtils.getAttributeCertificatesFromStore(attrCerts));
511            }
512
513            ASN1Set set = CMSUtils.createBerSetFromList(certs);
514
515            if (set.size() != 0)
516            {
517                certSet = set;
518            }
519        }
520
521        if (crls != null)
522        {
523            ASN1Set set = CMSUtils.createBerSetFromList(CMSUtils.getCRLsFromStore(crls));
524
525            if (set.size() != 0)
526            {
527                crlSet = set;
528            }
529        }
530
531        //
532        // replace the CMS structure.
533        //
534        cms.signedData = new SignedData(signedData.signedData.getDigestAlgorithms(),
535                                   signedData.signedData.getEncapContentInfo(),
536                                   certSet,
537                                   crlSet,
538                                   signedData.signedData.getSignerInfos());
539
540        //
541        // replace the contentInfo with the new one
542        //
543        cms.contentInfo = new ContentInfo(cms.contentInfo.getContentType(), cms.signedData);
544
545        return cms;
546    }
547}
548