Home | History | Annotate | Download | only in cms
      1 package org.bouncycastle.cms;
      2 
      3 import java.io.IOException;
      4 import java.io.OutputStream;
      5 import java.util.ArrayList;
      6 import java.util.Enumeration;
      7 import java.util.Iterator;
      8 import java.util.List;
      9 
     10 import org.bouncycastle.asn1.ASN1Encodable;
     11 import org.bouncycastle.asn1.ASN1EncodableVector;
     12 import org.bouncycastle.asn1.ASN1Encoding;
     13 import org.bouncycastle.asn1.ASN1ObjectIdentifier;
     14 import org.bouncycastle.asn1.ASN1OctetString;
     15 import org.bouncycastle.asn1.ASN1Primitive;
     16 import org.bouncycastle.asn1.ASN1Set;
     17 import org.bouncycastle.asn1.DERNull;
     18 import org.bouncycastle.asn1.DERSet;
     19 import org.bouncycastle.asn1.cms.Attribute;
     20 import org.bouncycastle.asn1.cms.AttributeTable;
     21 import org.bouncycastle.asn1.cms.CMSAlgorithmProtection;
     22 import org.bouncycastle.asn1.cms.CMSAttributes;
     23 import org.bouncycastle.asn1.cms.IssuerAndSerialNumber;
     24 import org.bouncycastle.asn1.cms.SignerIdentifier;
     25 import org.bouncycastle.asn1.cms.SignerInfo;
     26 import org.bouncycastle.asn1.cms.Time;
     27 import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
     28 import org.bouncycastle.asn1.x509.DigestInfo;
     29 import org.bouncycastle.cert.X509CertificateHolder;
     30 import org.bouncycastle.operator.ContentVerifier;
     31 import org.bouncycastle.operator.DigestCalculator;
     32 import org.bouncycastle.operator.OperatorCreationException;
     33 import org.bouncycastle.operator.RawContentVerifier;
     34 import org.bouncycastle.util.Arrays;
     35 import org.bouncycastle.util.io.TeeOutputStream;
     36 
     37 /**
     38  * an expanded SignerInfo block from a CMS Signed message
     39  */
     40 public class SignerInformation
     41 {
     42     private final SignerId                sid;
     43     private final CMSProcessable          content;
     44     private final byte[]                  signature;
     45     private final ASN1ObjectIdentifier    contentType;
     46     private final boolean                 isCounterSignature;
     47 
     48     // Derived
     49     private AttributeTable                signedAttributeValues;
     50     private AttributeTable                unsignedAttributeValues;
     51     private byte[]                        resultDigest;
     52 
     53     protected final SignerInfo            info;
     54     protected final AlgorithmIdentifier   digestAlgorithm;
     55     protected final AlgorithmIdentifier   encryptionAlgorithm;
     56     protected final ASN1Set               signedAttributeSet;
     57     protected final ASN1Set               unsignedAttributeSet;
     58 
     59     SignerInformation(
     60         SignerInfo          info,
     61         ASN1ObjectIdentifier contentType,
     62         CMSProcessable      content,
     63         byte[]              resultDigest)
     64     {
     65         this.info = info;
     66         this.contentType = contentType;
     67         this.isCounterSignature = contentType == null;
     68 
     69         SignerIdentifier   s = info.getSID();
     70 
     71         if (s.isTagged())
     72         {
     73             ASN1OctetString octs = ASN1OctetString.getInstance(s.getId());
     74 
     75             sid = new SignerId(octs.getOctets());
     76         }
     77         else
     78         {
     79             IssuerAndSerialNumber   iAnds = IssuerAndSerialNumber.getInstance(s.getId());
     80 
     81             sid = new SignerId(iAnds.getName(), iAnds.getSerialNumber().getValue());
     82         }
     83 
     84         this.digestAlgorithm = info.getDigestAlgorithm();
     85         this.signedAttributeSet = info.getAuthenticatedAttributes();
     86         this.unsignedAttributeSet = info.getUnauthenticatedAttributes();
     87         this.encryptionAlgorithm = info.getDigestEncryptionAlgorithm();
     88         this.signature = info.getEncryptedDigest().getOctets();
     89 
     90         this.content = content;
     91         this.resultDigest = resultDigest;
     92     }
     93 
     94     /**
     95      * Protected constructor. In some cases clients have their own idea about how to encode
     96      * the signed attributes and calculate the signature. This constructor is to allow developers
     97      * to deal with that by extending off the class and overridng methods like getSignedAttributes().
     98      *
     99      * @param baseInfo the SignerInformation to base this one on.
    100      */
    101     protected SignerInformation(SignerInformation baseInfo)
    102     {
    103         this.info = baseInfo.info;
    104         this.contentType = baseInfo.contentType;
    105         this.isCounterSignature = baseInfo.isCounterSignature();
    106         this.sid = baseInfo.getSID();
    107         this.digestAlgorithm = info.getDigestAlgorithm();
    108         this.signedAttributeSet = info.getAuthenticatedAttributes();
    109         this.unsignedAttributeSet = info.getUnauthenticatedAttributes();
    110         this.encryptionAlgorithm = info.getDigestEncryptionAlgorithm();
    111         this.signature = info.getEncryptedDigest().getOctets();
    112         this.content = baseInfo.content;
    113         this.resultDigest = baseInfo.resultDigest;
    114     }
    115 
    116     public boolean isCounterSignature()
    117     {
    118         return isCounterSignature;
    119     }
    120 
    121     public ASN1ObjectIdentifier getContentType()
    122     {
    123         return this.contentType;
    124     }
    125 
    126     private byte[] encodeObj(
    127         ASN1Encodable    obj)
    128         throws IOException
    129     {
    130         if (obj != null)
    131         {
    132             return obj.toASN1Primitive().getEncoded();
    133         }
    134 
    135         return null;
    136     }
    137 
    138     public SignerId getSID()
    139     {
    140         return sid;
    141     }
    142 
    143     /**
    144      * return the version number for this objects underlying SignerInfo structure.
    145      */
    146     public int getVersion()
    147     {
    148         return info.getVersion().getValue().intValue();
    149     }
    150 
    151     public AlgorithmIdentifier getDigestAlgorithmID()
    152     {
    153         return digestAlgorithm;
    154     }
    155 
    156     /**
    157      * return the object identifier for the signature.
    158      */
    159     public String getDigestAlgOID()
    160     {
    161         return digestAlgorithm.getAlgorithm().getId();
    162     }
    163 
    164     /**
    165      * return the signature parameters, or null if there aren't any.
    166      */
    167     public byte[] getDigestAlgParams()
    168     {
    169         try
    170         {
    171             return encodeObj(digestAlgorithm.getParameters());
    172         }
    173         catch (Exception e)
    174         {
    175             throw new RuntimeException("exception getting digest parameters " + e);
    176         }
    177     }
    178 
    179     /**
    180      * return the content digest that was calculated during verification.
    181      */
    182     public byte[] getContentDigest()
    183     {
    184         if (resultDigest == null)
    185         {
    186             throw new IllegalStateException("method can only be called after verify.");
    187         }
    188 
    189         return Arrays.clone(resultDigest);
    190     }
    191 
    192     /**
    193      * return the object identifier for the signature.
    194      */
    195     public String getEncryptionAlgOID()
    196     {
    197         return encryptionAlgorithm.getAlgorithm().getId();
    198     }
    199 
    200     /**
    201      * return the signature/encryption algorithm parameters, or null if
    202      * there aren't any.
    203      */
    204     public byte[] getEncryptionAlgParams()
    205     {
    206         try
    207         {
    208             return encodeObj(encryptionAlgorithm.getParameters());
    209         }
    210         catch (Exception e)
    211         {
    212             throw new RuntimeException("exception getting encryption parameters " + e);
    213         }
    214     }
    215 
    216     /**
    217      * return a table of the signed attributes - indexed by
    218      * the OID of the attribute.
    219      */
    220     public AttributeTable getSignedAttributes()
    221     {
    222         if (signedAttributeSet != null && signedAttributeValues == null)
    223         {
    224             signedAttributeValues = new AttributeTable(signedAttributeSet);
    225         }
    226 
    227         return signedAttributeValues;
    228     }
    229 
    230     /**
    231      * return a table of the unsigned attributes indexed by
    232      * the OID of the attribute.
    233      */
    234     public AttributeTable getUnsignedAttributes()
    235     {
    236         if (unsignedAttributeSet != null && unsignedAttributeValues == null)
    237         {
    238             unsignedAttributeValues = new AttributeTable(unsignedAttributeSet);
    239         }
    240 
    241         return unsignedAttributeValues;
    242     }
    243 
    244     /**
    245      * return the encoded signature
    246      */
    247     public byte[] getSignature()
    248     {
    249         return Arrays.clone(signature);
    250     }
    251 
    252     /**
    253      * Return a SignerInformationStore containing the counter signatures attached to this
    254      * signer. If no counter signatures are present an empty store is returned.
    255      */
    256     public SignerInformationStore getCounterSignatures()
    257     {
    258         // TODO There are several checks implied by the RFC3852 comments that are missing
    259 
    260         /*
    261         The countersignature attribute MUST be an unsigned attribute; it MUST
    262         NOT be a signed attribute, an authenticated attribute, an
    263         unauthenticated attribute, or an unprotected attribute.
    264         */
    265         AttributeTable unsignedAttributeTable = getUnsignedAttributes();
    266         if (unsignedAttributeTable == null)
    267         {
    268             return new SignerInformationStore(new ArrayList(0));
    269         }
    270 
    271         List counterSignatures = new ArrayList();
    272 
    273         /*
    274         The UnsignedAttributes syntax is defined as a SET OF Attributes.  The
    275         UnsignedAttributes in a signerInfo may include multiple instances of
    276         the countersignature attribute.
    277         */
    278         ASN1EncodableVector allCSAttrs = unsignedAttributeTable.getAll(CMSAttributes.counterSignature);
    279 
    280         for (int i = 0; i < allCSAttrs.size(); ++i)
    281         {
    282             Attribute counterSignatureAttribute = (Attribute)allCSAttrs.get(i);
    283 
    284             /*
    285             A countersignature attribute can have multiple attribute values.  The
    286             syntax is defined as a SET OF AttributeValue, and there MUST be one
    287             or more instances of AttributeValue present.
    288             */
    289             ASN1Set values = counterSignatureAttribute.getAttrValues();
    290             if (values.size() < 1)
    291             {
    292                 // TODO Throw an appropriate exception?
    293             }
    294 
    295             for (Enumeration en = values.getObjects(); en.hasMoreElements();)
    296             {
    297                 /*
    298                 Countersignature values have the same meaning as SignerInfo values
    299                 for ordinary signatures, except that:
    300 
    301                    1. The signedAttributes field MUST NOT contain a content-type
    302                       attribute; there is no content type for countersignatures.
    303 
    304                    2. The signedAttributes field MUST contain a message-digest
    305                       attribute if it contains any other attributes.
    306 
    307                    3. The input to the message-digesting process is the contents
    308                       octets of the DER encoding of the signatureValue field of the
    309                       SignerInfo value with which the attribute is associated.
    310                 */
    311                 SignerInfo si = SignerInfo.getInstance(en.nextElement());
    312 
    313                 counterSignatures.add(new SignerInformation(si, null, new CMSProcessableByteArray(getSignature()), null));
    314             }
    315         }
    316 
    317         return new SignerInformationStore(counterSignatures);
    318     }
    319 
    320     /**
    321      * return the DER encoding of the signed attributes.
    322      * @throws IOException if an encoding error occurs.
    323      */
    324     public byte[] getEncodedSignedAttributes()
    325         throws IOException
    326     {
    327         if (signedAttributeSet != null)
    328         {
    329             return signedAttributeSet.getEncoded(ASN1Encoding.DER);
    330         }
    331 
    332         return null;
    333     }
    334 
    335     private boolean doVerify(
    336         SignerInformationVerifier verifier)
    337         throws CMSException
    338     {
    339         String          encName = CMSSignedHelper.INSTANCE.getEncryptionAlgName(this.getEncryptionAlgOID());
    340         ContentVerifier contentVerifier;
    341 
    342         try
    343         {
    344             contentVerifier = verifier.getContentVerifier(encryptionAlgorithm, info.getDigestAlgorithm());
    345         }
    346         catch (OperatorCreationException e)
    347         {
    348             throw new CMSException("can't create content verifier: " + e.getMessage(), e);
    349         }
    350 
    351         try
    352         {
    353             OutputStream sigOut = contentVerifier.getOutputStream();
    354 
    355             if (resultDigest == null)
    356             {
    357                 DigestCalculator calc = verifier.getDigestCalculator(this.getDigestAlgorithmID());
    358                 if (content != null)
    359                 {
    360                     OutputStream      digOut = calc.getOutputStream();
    361 
    362                     if (signedAttributeSet == null)
    363                     {
    364                         if (contentVerifier instanceof RawContentVerifier)
    365                         {
    366                             content.write(digOut);
    367                         }
    368                         else
    369                         {
    370                             OutputStream cOut = new TeeOutputStream(digOut, sigOut);
    371 
    372                             content.write(cOut);
    373 
    374                             cOut.close();
    375                         }
    376                     }
    377                     else
    378                     {
    379                         content.write(digOut);
    380                         sigOut.write(this.getEncodedSignedAttributes());
    381                     }
    382 
    383                     digOut.close();
    384                 }
    385                 else if (signedAttributeSet != null)
    386                 {
    387                     sigOut.write(this.getEncodedSignedAttributes());
    388                 }
    389                 else
    390                 {
    391                     // TODO Get rid of this exception and just treat content==null as empty not missing?
    392                     throw new CMSException("data not encapsulated in signature - use detached constructor.");
    393                 }
    394 
    395                 resultDigest = calc.getDigest();
    396             }
    397             else
    398             {
    399                 if (signedAttributeSet == null)
    400                 {
    401                     if (content != null)
    402                     {
    403                         content.write(sigOut);
    404                     }
    405                 }
    406                 else
    407                 {
    408                     sigOut.write(this.getEncodedSignedAttributes());
    409                 }
    410             }
    411 
    412             sigOut.close();
    413         }
    414         catch (IOException e)
    415         {
    416             throw new CMSException("can't process mime object to create signature.", e);
    417         }
    418         catch (OperatorCreationException e)
    419         {
    420             throw new CMSException("can't create digest calculator: " + e.getMessage(), e);
    421         }
    422 
    423         // RFC 3852 11.1 Check the content-type attribute is correct
    424         {
    425             ASN1Primitive validContentType = getSingleValuedSignedAttribute(
    426                 CMSAttributes.contentType, "content-type");
    427             if (validContentType == null)
    428             {
    429                 if (!isCounterSignature && signedAttributeSet != null)
    430                 {
    431                     throw new CMSException("The content-type attribute type MUST be present whenever signed attributes are present in signed-data");
    432                 }
    433             }
    434             else
    435             {
    436                 if (isCounterSignature)
    437                 {
    438                     throw new CMSException("[For counter signatures,] the signedAttributes field MUST NOT contain a content-type attribute");
    439                 }
    440 
    441                 if (!(validContentType instanceof ASN1ObjectIdentifier))
    442                 {
    443                     throw new CMSException("content-type attribute value not of ASN.1 type 'OBJECT IDENTIFIER'");
    444                 }
    445 
    446                 ASN1ObjectIdentifier signedContentType = (ASN1ObjectIdentifier)validContentType;
    447 
    448                 if (!signedContentType.equals(contentType))
    449                 {
    450                     throw new CMSException("content-type attribute value does not match eContentType");
    451                 }
    452             }
    453         }
    454 
    455         AttributeTable signedAttrTable = this.getSignedAttributes();
    456 
    457         // RFC 6211 Validate Algorithm Identifier protection attribute if present
    458         {
    459             AttributeTable unsignedAttrTable = this.getUnsignedAttributes();
    460             if (unsignedAttrTable != null && unsignedAttrTable.getAll(CMSAttributes.cmsAlgorithmProtect).size() > 0)
    461             {
    462                 throw new CMSException("A cmsAlgorithmProtect attribute MUST be a signed attribute");
    463             }
    464             if (signedAttrTable != null)
    465             {
    466                 ASN1EncodableVector protectionAttributes = signedAttrTable.getAll(CMSAttributes.cmsAlgorithmProtect);
    467                 if (protectionAttributes.size() > 1)
    468                 {
    469                     throw new CMSException("Only one instance of a cmsAlgorithmProtect attribute can be present");
    470                 }
    471 
    472                 if (protectionAttributes.size() > 0)
    473                 {
    474                     Attribute attr = Attribute.getInstance(protectionAttributes.get(0));
    475                     if (attr.getAttrValues().size() != 1)
    476                     {
    477                         throw new CMSException("A cmsAlgorithmProtect attribute MUST contain exactly one value");
    478                     }
    479 
    480                     CMSAlgorithmProtection algorithmProtection = CMSAlgorithmProtection.getInstance(attr.getAttributeValues()[0]);
    481 
    482                     if (!CMSUtils.isEquivalent(algorithmProtection.getDigestAlgorithm(), info.getDigestAlgorithm()))
    483                     {
    484                         throw new CMSException("CMS Algorithm Identifier Protection check failed for digestAlgorithm");
    485                     }
    486 
    487                     if (!CMSUtils.isEquivalent(algorithmProtection.getSignatureAlgorithm(), info.getDigestEncryptionAlgorithm()))
    488                     {
    489                         throw new CMSException("CMS Algorithm Identifier Protection check failed for signatureAlgorithm");
    490                     }
    491                 }
    492             }
    493         }
    494 
    495         // RFC 3852 11.2 Check the message-digest attribute is correct
    496         {
    497             ASN1Primitive validMessageDigest = getSingleValuedSignedAttribute(
    498                 CMSAttributes.messageDigest, "message-digest");
    499             if (validMessageDigest == null)
    500             {
    501                 if (signedAttributeSet != null)
    502                 {
    503                     throw new CMSException("the message-digest signed attribute type MUST be present when there are any signed attributes present");
    504                 }
    505             }
    506             else
    507             {
    508                 if (!(validMessageDigest instanceof ASN1OctetString))
    509                 {
    510                     throw new CMSException("message-digest attribute value not of ASN.1 type 'OCTET STRING'");
    511                 }
    512 
    513                 ASN1OctetString signedMessageDigest = (ASN1OctetString)validMessageDigest;
    514 
    515                 if (!Arrays.constantTimeAreEqual(resultDigest, signedMessageDigest.getOctets()))
    516                 {
    517                     throw new CMSSignerDigestMismatchException("message-digest attribute value does not match calculated value");
    518                 }
    519             }
    520         }
    521 
    522         // RFC 3852 11.4 Validate countersignature attribute(s)
    523         {
    524             if (signedAttrTable != null
    525                 && signedAttrTable.getAll(CMSAttributes.counterSignature).size() > 0)
    526             {
    527                 throw new CMSException("A countersignature attribute MUST NOT be a signed attribute");
    528             }
    529 
    530             AttributeTable unsignedAttrTable = this.getUnsignedAttributes();
    531             if (unsignedAttrTable != null)
    532             {
    533                 ASN1EncodableVector csAttrs = unsignedAttrTable.getAll(CMSAttributes.counterSignature);
    534                 for (int i = 0; i < csAttrs.size(); ++i)
    535                 {
    536                     Attribute csAttr = Attribute.getInstance(csAttrs.get(i));
    537                     if (csAttr.getAttrValues().size() < 1)
    538                     {
    539                         throw new CMSException("A countersignature attribute MUST contain at least one AttributeValue");
    540                     }
    541 
    542                     // Note: We don't recursively validate the countersignature value
    543                 }
    544             }
    545         }
    546 
    547         try
    548         {
    549             if (signedAttributeSet == null && resultDigest != null)
    550             {
    551                 if (contentVerifier instanceof RawContentVerifier)
    552                 {
    553                     RawContentVerifier rawVerifier = (RawContentVerifier)contentVerifier;
    554 
    555                     if (encName.equals("RSA"))
    556                     {
    557                         DigestInfo digInfo = new DigestInfo(new AlgorithmIdentifier(digestAlgorithm.getAlgorithm(), DERNull.INSTANCE), resultDigest);
    558 
    559                         return rawVerifier.verify(digInfo.getEncoded(ASN1Encoding.DER), this.getSignature());
    560                     }
    561 
    562                     return rawVerifier.verify(resultDigest, this.getSignature());
    563                 }
    564             }
    565 
    566             return contentVerifier.verify(this.getSignature());
    567         }
    568         catch (IOException e)
    569         {
    570             throw new CMSException("can't process mime object to create signature.", e);
    571         }
    572     }
    573 
    574     /**
    575      * Verify that the given verifier can successfully verify the signature on
    576      * this SignerInformation object.
    577      *
    578      * @param verifier a suitably configured SignerInformationVerifier.
    579      * @return true if the signer information is verified, false otherwise.
    580      * @throws org.bouncycastle.cms.CMSVerifierCertificateNotValidException if the provider has an associated certificate and the certificate is not valid at the time given as the SignerInfo's signing time.
    581      * @throws org.bouncycastle.cms.CMSException if the verifier is unable to create a ContentVerifiers or DigestCalculators.
    582      */
    583     public boolean verify(SignerInformationVerifier verifier)
    584         throws CMSException
    585     {
    586         Time signingTime = getSigningTime();   // has to be validated if present.
    587 
    588         if (verifier.hasAssociatedCertificate())
    589         {
    590             if (signingTime != null)
    591             {
    592                 X509CertificateHolder dcv = verifier.getAssociatedCertificate();
    593 
    594                 if (!dcv.isValidOn(signingTime.getDate()))
    595                 {
    596                     throw new CMSVerifierCertificateNotValidException("verifier not valid at signingTime");
    597                 }
    598             }
    599         }
    600 
    601         return doVerify(verifier);
    602     }
    603 
    604     /**
    605      * Return the underlying ASN.1 object defining this SignerInformation object.
    606      *
    607      * @return a SignerInfo.
    608      */
    609     public SignerInfo toASN1Structure()
    610     {
    611         return info;
    612     }
    613 
    614     private ASN1Primitive getSingleValuedSignedAttribute(
    615         ASN1ObjectIdentifier attrOID, String printableName)
    616         throws CMSException
    617     {
    618         AttributeTable unsignedAttrTable = this.getUnsignedAttributes();
    619         if (unsignedAttrTable != null
    620             && unsignedAttrTable.getAll(attrOID).size() > 0)
    621         {
    622             throw new CMSException("The " + printableName
    623                 + " attribute MUST NOT be an unsigned attribute");
    624         }
    625 
    626         AttributeTable signedAttrTable = this.getSignedAttributes();
    627         if (signedAttrTable == null)
    628         {
    629             return null;
    630         }
    631 
    632         ASN1EncodableVector v = signedAttrTable.getAll(attrOID);
    633         switch (v.size())
    634         {
    635             case 0:
    636                 return null;
    637             case 1:
    638             {
    639                 Attribute t = (Attribute)v.get(0);
    640                 ASN1Set attrValues = t.getAttrValues();
    641                 if (attrValues.size() != 1)
    642                 {
    643                     throw new CMSException("A " + printableName
    644                         + " attribute MUST have a single attribute value");
    645                 }
    646 
    647                 return attrValues.getObjectAt(0).toASN1Primitive();
    648             }
    649             default:
    650                 throw new CMSException("The SignedAttributes in a signerInfo MUST NOT include multiple instances of the "
    651                     + printableName + " attribute");
    652         }
    653     }
    654 
    655     private Time getSigningTime() throws CMSException
    656     {
    657         ASN1Primitive validSigningTime = getSingleValuedSignedAttribute(
    658             CMSAttributes.signingTime, "signing-time");
    659 
    660         if (validSigningTime == null)
    661         {
    662             return null;
    663         }
    664 
    665         try
    666         {
    667             return Time.getInstance(validSigningTime);
    668         }
    669         catch (IllegalArgumentException e)
    670         {
    671             throw new CMSException("signing-time attribute value not a valid 'Time' structure");
    672         }
    673     }
    674 
    675     /**
    676      * Return a signer information object with the passed in unsigned
    677      * attributes replacing the ones that are current associated with
    678      * the object passed in.
    679      *
    680      * @param signerInformation the signerInfo to be used as the basis.
    681      * @param unsignedAttributes the unsigned attributes to add.
    682      * @return a copy of the original SignerInformationObject with the changed attributes.
    683      */
    684     public static SignerInformation replaceUnsignedAttributes(
    685         SignerInformation   signerInformation,
    686         AttributeTable      unsignedAttributes)
    687     {
    688         SignerInfo  sInfo = signerInformation.info;
    689         ASN1Set     unsignedAttr = null;
    690 
    691         if (unsignedAttributes != null)
    692         {
    693             unsignedAttr = new DERSet(unsignedAttributes.toASN1EncodableVector());
    694         }
    695 
    696         return new SignerInformation(
    697                 new SignerInfo(sInfo.getSID(), sInfo.getDigestAlgorithm(),
    698                     sInfo.getAuthenticatedAttributes(), sInfo.getDigestEncryptionAlgorithm(), sInfo.getEncryptedDigest(), unsignedAttr),
    699                     signerInformation.contentType, signerInformation.content, null);
    700     }
    701 
    702     /**
    703      * Return a signer information object with passed in SignerInformationStore representing counter
    704      * signatures attached as an unsigned attribute.
    705      *
    706      * @param signerInformation the signerInfo to be used as the basis.
    707      * @param counterSigners signer info objects carrying counter signature.
    708      * @return a copy of the original SignerInformationObject with the changed attributes.
    709      */
    710     public static SignerInformation addCounterSigners(
    711         SignerInformation        signerInformation,
    712         SignerInformationStore   counterSigners)
    713     {
    714         // TODO Perform checks from RFC 3852 11.4
    715 
    716         SignerInfo          sInfo = signerInformation.info;
    717         AttributeTable      unsignedAttr = signerInformation.getUnsignedAttributes();
    718         ASN1EncodableVector v;
    719 
    720         if (unsignedAttr != null)
    721         {
    722             v = unsignedAttr.toASN1EncodableVector();
    723         }
    724         else
    725         {
    726             v = new ASN1EncodableVector();
    727         }
    728 
    729         ASN1EncodableVector sigs = new ASN1EncodableVector();
    730 
    731         for (Iterator it = counterSigners.getSigners().iterator(); it.hasNext();)
    732         {
    733             sigs.add(((SignerInformation)it.next()).toASN1Structure());
    734         }
    735 
    736         v.add(new Attribute(CMSAttributes.counterSignature, new DERSet(sigs)));
    737 
    738         return new SignerInformation(
    739                 new SignerInfo(sInfo.getSID(), sInfo.getDigestAlgorithm(),
    740                     sInfo.getAuthenticatedAttributes(), sInfo.getDigestEncryptionAlgorithm(), sInfo.getEncryptedDigest(), new DERSet(v)),
    741                     signerInformation.contentType, signerInformation.content, null);
    742     }
    743 }
    744