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