• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
SignerInformation( SignerInfo info, ASN1ObjectIdentifier contentType, CMSProcessable content, byte[] resultDigest)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      */
SignerInformation(SignerInformation baseInfo)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 
isCounterSignature()116     public boolean isCounterSignature()
117     {
118         return isCounterSignature;
119     }
120 
getContentType()121     public ASN1ObjectIdentifier getContentType()
122     {
123         return this.contentType;
124     }
125 
encodeObj( ASN1Encodable obj)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 
getSID()138     public SignerId getSID()
139     {
140         return sid;
141     }
142 
143     /**
144      * return the version number for this objects underlying SignerInfo structure.
145      */
getVersion()146     public int getVersion()
147     {
148         return info.getVersion().getValue().intValue();
149     }
150 
getDigestAlgorithmID()151     public AlgorithmIdentifier getDigestAlgorithmID()
152     {
153         return digestAlgorithm;
154     }
155 
156     /**
157      * return the object identifier for the signature.
158      */
getDigestAlgOID()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      */
getDigestAlgParams()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      */
getContentDigest()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      */
getEncryptionAlgOID()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      */
getEncryptionAlgParams()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      */
getSignedAttributes()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      */
getUnsignedAttributes()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      */
getSignature()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      */
getCounterSignatures()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      */
getEncodedSignedAttributes()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 
doVerify( SignerInformationVerifier verifier)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      */
verify(SignerInformationVerifier verifier)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      */
toASN1Structure()609     public SignerInfo toASN1Structure()
610     {
611         return info;
612     }
613 
getSingleValuedSignedAttribute( ASN1ObjectIdentifier attrOID, String printableName)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 
getSigningTime()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      */
replaceUnsignedAttributes( SignerInformation signerInformation, AttributeTable unsignedAttributes)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      */
addCounterSigners( SignerInformation signerInformation, SignerInformationStore counterSigners)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