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