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