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