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