1 package org.bouncycastle.cms; 2 3 import java.io.IOException; 4 import java.io.InputStream; 5 import java.io.OutputStream; 6 import java.util.ArrayList; 7 import java.util.Collection; 8 import java.util.Collections; 9 import java.util.Enumeration; 10 import java.util.HashSet; 11 import java.util.Iterator; 12 import java.util.List; 13 import java.util.Map; 14 import java.util.Set; 15 16 import org.bouncycastle.asn1.ASN1Encodable; 17 import org.bouncycastle.asn1.ASN1EncodableVector; 18 import org.bouncycastle.asn1.ASN1InputStream; 19 import org.bouncycastle.asn1.ASN1ObjectIdentifier; 20 import org.bouncycastle.asn1.ASN1OctetString; 21 import org.bouncycastle.asn1.ASN1Sequence; 22 import org.bouncycastle.asn1.ASN1Set; 23 import org.bouncycastle.asn1.BERSequence; 24 import org.bouncycastle.asn1.DERSet; 25 import org.bouncycastle.asn1.cms.ContentInfo; 26 import org.bouncycastle.asn1.cms.SignedData; 27 import org.bouncycastle.asn1.cms.SignerInfo; 28 import org.bouncycastle.asn1.x509.AlgorithmIdentifier; 29 import org.bouncycastle.cert.X509AttributeCertificateHolder; 30 import org.bouncycastle.cert.X509CRLHolder; 31 import org.bouncycastle.cert.X509CertificateHolder; 32 import org.bouncycastle.operator.OperatorCreationException; 33 import org.bouncycastle.util.Encodable; 34 import org.bouncycastle.util.Store; 35 36 /** 37 * general class for handling a pkcs7-signature message. 38 * 39 * A simple example of usage - note, in the example below the validity of 40 * the certificate isn't verified, just the fact that one of the certs 41 * matches the given signer... 42 * 43 * <pre> 44 * Store certStore = s.getCertificates(); 45 * SignerInformationStore signers = s.getSignerInfos(); 46 * Collection c = signers.getSigners(); 47 * Iterator it = c.iterator(); 48 * 49 * while (it.hasNext()) 50 * { 51 * SignerInformation signer = (SignerInformation)it.next(); 52 * Collection certCollection = certStore.getMatches(signer.getSID()); 53 * 54 * Iterator certIt = certCollection.iterator(); 55 * X509CertificateHolder cert = (X509CertificateHolder)certIt.next(); 56 * 57 * if (signer.verify(new JcaSimpleSignerInfoVerifierBuilder().setProvider("BC").build(cert))) 58 * { 59 * verified++; 60 * } 61 * } 62 * </pre> 63 */ 64 public class CMSSignedData 65 implements Encodable 66 { 67 private static final CMSSignedHelper HELPER = CMSSignedHelper.INSTANCE; 68 69 SignedData signedData; 70 ContentInfo contentInfo; 71 CMSTypedData signedContent; 72 SignerInformationStore signerInfoStore; 73 74 private Map hashes; 75 76 private CMSSignedData( 77 CMSSignedData c) 78 { 79 this.signedData = c.signedData; 80 this.contentInfo = c.contentInfo; 81 this.signedContent = c.signedContent; 82 this.signerInfoStore = c.signerInfoStore; 83 } 84 85 public CMSSignedData( 86 byte[] sigBlock) 87 throws CMSException 88 { 89 this(CMSUtils.readContentInfo(sigBlock)); 90 } 91 92 public CMSSignedData( 93 CMSProcessable signedContent, 94 byte[] sigBlock) 95 throws CMSException 96 { 97 this(signedContent, CMSUtils.readContentInfo(sigBlock)); 98 } 99 100 /** 101 * Content with detached signature, digests precomputed 102 * 103 * @param hashes a map of precomputed digests for content indexed by name of hash. 104 * @param sigBlock the signature object. 105 */ 106 public CMSSignedData( 107 Map hashes, 108 byte[] sigBlock) 109 throws CMSException 110 { 111 this(hashes, CMSUtils.readContentInfo(sigBlock)); 112 } 113 114 /** 115 * base constructor - content with detached signature. 116 * 117 * @param signedContent the content that was signed. 118 * @param sigData the signature object. 119 */ 120 public CMSSignedData( 121 CMSProcessable signedContent, 122 InputStream sigData) 123 throws CMSException 124 { 125 this(signedContent, CMSUtils.readContentInfo(new ASN1InputStream(sigData))); 126 } 127 128 /** 129 * base constructor - with encapsulated content 130 */ 131 public CMSSignedData( 132 InputStream sigData) 133 throws CMSException 134 { 135 this(CMSUtils.readContentInfo(sigData)); 136 } 137 138 public CMSSignedData( 139 final CMSProcessable signedContent, 140 ContentInfo sigData) 141 throws CMSException 142 { 143 if (signedContent instanceof CMSTypedData) 144 { 145 this.signedContent = (CMSTypedData)signedContent; 146 } 147 else 148 { 149 this.signedContent = new CMSTypedData() 150 { 151 public ASN1ObjectIdentifier getContentType() 152 { 153 return signedData.getEncapContentInfo().getContentType(); 154 } 155 156 public void write(OutputStream out) 157 throws IOException, CMSException 158 { 159 signedContent.write(out); 160 } 161 162 public Object getContent() 163 { 164 return signedContent.getContent(); 165 } 166 }; 167 } 168 169 this.contentInfo = sigData; 170 this.signedData = getSignedData(); 171 } 172 173 public CMSSignedData( 174 Map hashes, 175 ContentInfo sigData) 176 throws CMSException 177 { 178 this.hashes = hashes; 179 this.contentInfo = sigData; 180 this.signedData = getSignedData(); 181 } 182 183 public CMSSignedData( 184 ContentInfo sigData) 185 throws CMSException 186 { 187 this.contentInfo = sigData; 188 this.signedData = getSignedData(); 189 190 // 191 // this can happen if the signed message is sent simply to send a 192 // certificate chain. 193 // 194 ASN1Encodable content = signedData.getEncapContentInfo().getContent(); 195 if (content != null) 196 { 197 if (content instanceof ASN1OctetString) 198 { 199 this.signedContent = new CMSProcessableByteArray(signedData.getEncapContentInfo().getContentType(), 200 ((ASN1OctetString)content).getOctets()); 201 } 202 else 203 { 204 this.signedContent = new PKCS7ProcessableObject(signedData.getEncapContentInfo().getContentType(), content); 205 } 206 } 207 else 208 { 209 this.signedContent = null; 210 } 211 } 212 213 private SignedData getSignedData() 214 throws CMSException 215 { 216 try 217 { 218 return SignedData.getInstance(contentInfo.getContent()); 219 } 220 catch (ClassCastException e) 221 { 222 throw new CMSException("Malformed content.", e); 223 } 224 catch (IllegalArgumentException e) 225 { 226 throw new CMSException("Malformed content.", e); 227 } 228 } 229 230 /** 231 * Return the version number for this object 232 */ 233 public int getVersion() 234 { 235 return signedData.getVersion().getValue().intValue(); 236 } 237 238 /** 239 * return the collection of signers that are associated with the 240 * signatures for the message. 241 */ 242 public SignerInformationStore getSignerInfos() 243 { 244 if (signerInfoStore == null) 245 { 246 ASN1Set s = signedData.getSignerInfos(); 247 List signerInfos = new ArrayList(); 248 249 for (int i = 0; i != s.size(); i++) 250 { 251 SignerInfo info = SignerInfo.getInstance(s.getObjectAt(i)); 252 ASN1ObjectIdentifier contentType = signedData.getEncapContentInfo().getContentType(); 253 254 if (hashes == null) 255 { 256 signerInfos.add(new SignerInformation(info, contentType, signedContent, null)); 257 } 258 else 259 { 260 Object obj = hashes.keySet().iterator().next(); 261 byte[] hash = (obj instanceof String) ? (byte[])hashes.get(info.getDigestAlgorithm().getAlgorithm().getId()) : (byte[])hashes.get(info.getDigestAlgorithm().getAlgorithm()); 262 263 signerInfos.add(new SignerInformation(info, contentType, null, hash)); 264 } 265 } 266 267 signerInfoStore = new SignerInformationStore(signerInfos); 268 } 269 270 return signerInfoStore; 271 } 272 273 /** 274 * Return if this is object represents a detached signature. 275 * 276 * @return true if this message represents a detached signature, false otherwise. 277 */ 278 public boolean isDetachedSignature() 279 { 280 return signedData.getEncapContentInfo().getContent() == null && signedData.getSignerInfos().size() > 0; 281 } 282 283 /** 284 * Return if this is object represents a certificate management message. 285 * 286 * @return true if the message has no signers or content, false otherwise. 287 */ 288 public boolean isCertificateManagementMessage() 289 { 290 return signedData.getEncapContentInfo().getContent() == null && signedData.getSignerInfos().size() == 0; 291 } 292 293 /** 294 * Return any X.509 certificate objects in this SignedData structure as a Store of X509CertificateHolder objects. 295 * 296 * @return a Store of X509CertificateHolder objects. 297 */ 298 public Store<X509CertificateHolder> getCertificates() 299 { 300 return HELPER.getCertificates(signedData.getCertificates()); 301 } 302 303 /** 304 * Return any X.509 CRL objects in this SignedData structure as a Store of X509CRLHolder objects. 305 * 306 * @return a Store of X509CRLHolder objects. 307 */ 308 public Store<X509CRLHolder> getCRLs() 309 { 310 return HELPER.getCRLs(signedData.getCRLs()); 311 } 312 313 /** 314 * Return any X.509 attribute certificate objects in this SignedData structure as a Store of X509AttributeCertificateHolder objects. 315 * 316 * @return a Store of X509AttributeCertificateHolder objects. 317 */ 318 public Store<X509AttributeCertificateHolder> getAttributeCertificates() 319 { 320 return HELPER.getAttributeCertificates(signedData.getCertificates()); 321 } 322 323 // BEGIN Android-removed: OtherRevocationInfoFormat isn't supported 324 /* 325 /** 326 * Return any OtherRevocationInfo OtherRevInfo objects of the type indicated by otherRevocationInfoFormat in 327 * this SignedData structure. 328 * 329 * @param otherRevocationInfoFormat OID of the format type been looked for. 330 * 331 * @return a Store of ASN1Encodable objects representing any objects of otherRevocationInfoFormat found. 332 * 333 public Store getOtherRevocationInfo(ASN1ObjectIdentifier otherRevocationInfoFormat) 334 { 335 return HELPER.getOtherRevocationInfo(otherRevocationInfoFormat, signedData.getCRLs()); 336 } 337 */ 338 // END Android-removed: OtherRevocationInfoFormat isn't supported 339 340 /** 341 * Return the digest algorithm identifiers for the SignedData object 342 * 343 * @return the set of digest algorithm identifiers 344 */ 345 public Set<AlgorithmIdentifier> getDigestAlgorithmIDs() 346 { 347 Set<AlgorithmIdentifier> digests = new HashSet<AlgorithmIdentifier>(signedData.getDigestAlgorithms().size()); 348 349 for (Enumeration en = signedData.getDigestAlgorithms().getObjects(); en.hasMoreElements();) 350 { 351 digests.add(AlgorithmIdentifier.getInstance(en.nextElement())); 352 } 353 354 return Collections.unmodifiableSet(digests); 355 } 356 357 /** 358 * Return the a string representation of the OID associated with the 359 * encapsulated content info structure carried in the signed data. 360 * 361 * @return the OID for the content type. 362 */ 363 public String getSignedContentTypeOID() 364 { 365 return signedData.getEncapContentInfo().getContentType().getId(); 366 } 367 368 public CMSTypedData getSignedContent() 369 { 370 return signedContent; 371 } 372 373 /** 374 * return the ContentInfo 375 */ 376 public ContentInfo toASN1Structure() 377 { 378 return contentInfo; 379 } 380 381 /** 382 * return the ASN.1 encoded representation of this object. 383 */ 384 public byte[] getEncoded() 385 throws IOException 386 { 387 return contentInfo.getEncoded(); 388 } 389 390 // BEGIN Android-removed: Unknown reason 391 /* 392 /** 393 * Verify all the SignerInformation objects and their associated counter signatures attached 394 * to this CMS SignedData object. 395 * 396 * @param verifierProvider a provider of SignerInformationVerifier objects. 397 * @return true if all verify, false otherwise. 398 * @throws CMSException if an exception occurs during the verification process. 399 * 400 public boolean verifySignatures(SignerInformationVerifierProvider verifierProvider) 401 throws CMSException 402 { 403 return verifySignatures(verifierProvider, false); 404 } 405 406 /** 407 * Verify all the SignerInformation objects and optionally their associated counter signatures attached 408 * to this CMS SignedData object. 409 * 410 * @param verifierProvider a provider of SignerInformationVerifier objects. 411 * @param ignoreCounterSignatures if true don't check counter signatures. If false check counter signatures as well. 412 * @return true if all verify, false otherwise. 413 * @throws CMSException if an exception occurs during the verification process. 414 * 415 public boolean verifySignatures(SignerInformationVerifierProvider verifierProvider, boolean ignoreCounterSignatures) 416 throws CMSException 417 { 418 Collection signers = this.getSignerInfos().getSigners(); 419 420 for (Iterator it = signers.iterator(); it.hasNext();) 421 { 422 SignerInformation signer = (SignerInformation)it.next(); 423 424 try 425 { 426 SignerInformationVerifier verifier = verifierProvider.get(signer.getSID()); 427 428 if (!signer.verify(verifier)) 429 { 430 return false; 431 } 432 433 if (!ignoreCounterSignatures) 434 { 435 Collection counterSigners = signer.getCounterSignatures().getSigners(); 436 437 for (Iterator cIt = counterSigners.iterator(); cIt.hasNext();) 438 { 439 if (!verifyCounterSignature((SignerInformation)cIt.next(), verifierProvider)) 440 { 441 return false; 442 } 443 } 444 } 445 } 446 catch (OperatorCreationException e) 447 { 448 throw new CMSException("failure in verifier provider: " + e.getMessage(), e); 449 } 450 } 451 452 return true; 453 } 454 455 private boolean verifyCounterSignature(SignerInformation counterSigner, SignerInformationVerifierProvider verifierProvider) 456 throws OperatorCreationException, CMSException 457 { 458 SignerInformationVerifier counterVerifier = verifierProvider.get(counterSigner.getSID()); 459 460 if (!counterSigner.verify(counterVerifier)) 461 { 462 return false; 463 } 464 465 Collection counterSigners = counterSigner.getCounterSignatures().getSigners(); 466 for (Iterator cIt = counterSigners.iterator(); cIt.hasNext();) 467 { 468 if (!verifyCounterSignature((SignerInformation)cIt.next(), verifierProvider)) 469 { 470 return false; 471 } 472 } 473 474 return true; 475 } 476 */ 477 // END Android-removed: Unknown reason 478 479 /** 480 * Replace the SignerInformation store associated with this 481 * CMSSignedData object with the new one passed in. You would 482 * probably only want to do this if you wanted to change the unsigned 483 * attributes associated with a signer, or perhaps delete one. 484 * 485 * @param signedData the signed data object to be used as a base. 486 * @param signerInformationStore the new signer information store to use. 487 * @return a new signed data object. 488 */ 489 public static CMSSignedData replaceSigners( 490 CMSSignedData signedData, 491 SignerInformationStore signerInformationStore) 492 { 493 // 494 // copy 495 // 496 CMSSignedData cms = new CMSSignedData(signedData); 497 498 // 499 // replace the store 500 // 501 cms.signerInfoStore = signerInformationStore; 502 503 // 504 // replace the signers in the SignedData object 505 // 506 ASN1EncodableVector digestAlgs = new ASN1EncodableVector(); 507 ASN1EncodableVector vec = new ASN1EncodableVector(); 508 509 Iterator it = signerInformationStore.getSigners().iterator(); 510 while (it.hasNext()) 511 { 512 SignerInformation signer = (SignerInformation)it.next(); 513 digestAlgs.add(CMSSignedHelper.INSTANCE.fixAlgID(signer.getDigestAlgorithmID())); 514 vec.add(signer.toASN1Structure()); 515 } 516 517 ASN1Set digests = new DERSet(digestAlgs); 518 ASN1Set signers = new DERSet(vec); 519 ASN1Sequence sD = (ASN1Sequence)signedData.signedData.toASN1Primitive(); 520 521 vec = new ASN1EncodableVector(); 522 523 // 524 // signers are the last item in the sequence. 525 // 526 vec.add(sD.getObjectAt(0)); // version 527 vec.add(digests); 528 529 for (int i = 2; i != sD.size() - 1; i++) 530 { 531 vec.add(sD.getObjectAt(i)); 532 } 533 534 vec.add(signers); 535 536 cms.signedData = SignedData.getInstance(new BERSequence(vec)); 537 538 // 539 // replace the contentInfo with the new one 540 // 541 cms.contentInfo = new ContentInfo(cms.contentInfo.getContentType(), cms.signedData); 542 543 return cms; 544 } 545 546 /** 547 * Replace the certificate and CRL information associated with this 548 * CMSSignedData object with the new one passed in. 549 * 550 * @param signedData the signed data object to be used as a base. 551 * @param certificates the new certificates to be used. 552 * @param attrCerts the new attribute certificates to be used. 553 * @param revocations the new CRLs to be used - a collection of X509CRLHolder objects, OtherRevocationInfoFormat, or both. 554 * @return a new signed data object. 555 * @exception CMSException if there is an error processing the CertStore 556 */ 557 public static CMSSignedData replaceCertificatesAndCRLs( 558 CMSSignedData signedData, 559 Store certificates, 560 Store attrCerts, 561 Store revocations) 562 throws CMSException 563 { 564 // 565 // copy 566 // 567 CMSSignedData cms = new CMSSignedData(signedData); 568 569 // 570 // replace the certs and revocations in the SignedData object 571 // 572 ASN1Set certSet = null; 573 ASN1Set crlSet = null; 574 575 if (certificates != null || attrCerts != null) 576 { 577 List certs = new ArrayList(); 578 579 if (certificates != null) 580 { 581 certs.addAll(CMSUtils.getCertificatesFromStore(certificates)); 582 } 583 if (attrCerts != null) 584 { 585 certs.addAll(CMSUtils.getAttributeCertificatesFromStore(attrCerts)); 586 } 587 588 ASN1Set set = CMSUtils.createBerSetFromList(certs); 589 590 if (set.size() != 0) 591 { 592 certSet = set; 593 } 594 } 595 596 if (revocations != null) 597 { 598 ASN1Set set = CMSUtils.createBerSetFromList(CMSUtils.getCRLsFromStore(revocations)); 599 600 if (set.size() != 0) 601 { 602 crlSet = set; 603 } 604 } 605 606 // 607 // replace the CMS structure. 608 // 609 cms.signedData = new SignedData(signedData.signedData.getDigestAlgorithms(), 610 signedData.signedData.getEncapContentInfo(), 611 certSet, 612 crlSet, 613 signedData.signedData.getSignerInfos()); 614 615 // 616 // replace the contentInfo with the new one 617 // 618 cms.contentInfo = new ContentInfo(cms.contentInfo.getContentType(), cms.signedData); 619 620 return cms; 621 } 622 } 623