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