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