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