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