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