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