001: package org.bouncycastle.cms;
002:
003: import org.bouncycastle.asn1.ASN1EncodableVector;
004: import org.bouncycastle.asn1.ASN1InputStream;
005: import org.bouncycastle.asn1.ASN1OctetStringParser;
006: import org.bouncycastle.asn1.ASN1SequenceParser;
007: import org.bouncycastle.asn1.ASN1Set;
008: import org.bouncycastle.asn1.ASN1SetParser;
009: import org.bouncycastle.asn1.ASN1StreamParser;
010: import org.bouncycastle.asn1.BEROctetStringGenerator;
011: import org.bouncycastle.asn1.BERSequenceGenerator;
012: import org.bouncycastle.asn1.BERSetParser;
013: import org.bouncycastle.asn1.BERTaggedObject;
014: import org.bouncycastle.asn1.DEREncodable;
015: import org.bouncycastle.asn1.DERNull;
016: import org.bouncycastle.asn1.DERObject;
017: import org.bouncycastle.asn1.DERObjectIdentifier;
018: import org.bouncycastle.asn1.DERSet;
019: import org.bouncycastle.asn1.DERTaggedObject;
020: import org.bouncycastle.asn1.DERTags;
021: import org.bouncycastle.asn1.cms.CMSObjectIdentifiers;
022: import org.bouncycastle.asn1.cms.ContentInfoParser;
023: import org.bouncycastle.asn1.cms.SignedDataParser;
024: import org.bouncycastle.asn1.cms.SignerInfo;
025: import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
026: import org.bouncycastle.x509.NoSuchStoreException;
027: import org.bouncycastle.x509.X509Store;
028:
029: import java.io.ByteArrayInputStream;
030: import java.io.IOException;
031: import java.io.InputStream;
032: import java.io.OutputStream;
033: import java.security.DigestInputStream;
034: import java.security.MessageDigest;
035: import java.security.NoSuchAlgorithmException;
036: import java.security.NoSuchProviderException;
037: import java.security.cert.CertStore;
038: import java.security.cert.CertStoreException;
039: import java.util.ArrayList;
040: import java.util.HashMap;
041: import java.util.Iterator;
042: import java.util.List;
043: import java.util.Map;
044:
045: /**
046: * Parsing class for an CMS Signed Data object from an input stream.
047: * <p>
048: * Note: that because we are in a streaming mode only one signer can be tried and it is important
049: * that the methods on the parser are called in the appropriate order.
050: * </p>
051: * <p>
052: * A simple example of usage for an encapsulated signature.
053: * </p>
054: * <p>
055: * Two notes: first, in the example below the validity of
056: * the certificate isn't verified, just the fact that one of the certs
057: * matches the given signer, and, second, because we are in a streaming
058: * mode the order of the operations is important.
059: * </p>
060: * <pre>
061: * CMSSignedDataParser sp = new CMSSignedDataParser(encapSigData);
062: *
063: * sp.getSignedContent().drain();
064: *
065: * CertStore certs = sp.getCertificatesAndCRLs("Collection", "BC");
066: * SignerInformationStore signers = sp.getSignerInfos();
067: *
068: * Collection c = signers.getSigners();
069: * Iterator it = c.iterator();
070: *
071: * while (it.hasNext())
072: * {
073: * SignerInformation signer = (SignerInformation)it.next();
074: * Collection certCollection = certs.getCertificates(signer.getSID());
075: *
076: * Iterator certIt = certCollection.iterator();
077: * X509Certificate cert = (X509Certificate)certIt.next();
078: *
079: * System.out.println("verify returns: " + signer.verify(cert, "BC"));
080: * }
081: * </pre>
082: * Note also: this class does not introduce buffering - if you are processing large files you should create
083: * the parser with:
084: * <pre>
085: * CMSSignedDataParser ep = new CMSSignedDataParser(new BufferedInputStream(encapSigData, bufSize));
086: * </pre>
087: * where bufSize is a suitably large buffer size.
088: */
089: public class CMSSignedDataParser extends CMSContentInfoParser {
090: private static CMSSignedHelper HELPER = CMSSignedHelper.INSTANCE;
091:
092: private SignedDataParser _signedData;
093: private CMSTypedStream _signedContent;
094: private Map _digests;
095:
096: private CertStore _certStore;
097: private SignerInformationStore _signerInfoStore;
098: private X509Store _attributeStore;
099: private ASN1Set _certSet, _crlSet;
100: private boolean _isCertCrlParsed;
101: private X509Store _certificateStore;
102: private X509Store _crlStore;
103:
104: public CMSSignedDataParser(byte[] sigBlock) throws CMSException {
105: this (new ByteArrayInputStream(sigBlock));
106: }
107:
108: public CMSSignedDataParser(CMSTypedStream signedContent,
109: byte[] sigBlock) throws CMSException {
110: this (signedContent, new ByteArrayInputStream(sigBlock));
111: }
112:
113: /**
114: * base constructor - with encapsulated content
115: */
116: public CMSSignedDataParser(InputStream sigData) throws CMSException {
117: this (null, sigData);
118: }
119:
120: /**
121: * base constructor
122: *
123: * @param signedContent the content that was signed.
124: * @param sigData the signature object stream.
125: */
126: public CMSSignedDataParser(CMSTypedStream signedContent,
127: InputStream sigData) throws CMSException {
128: super (sigData);
129:
130: try {
131: _signedContent = signedContent;
132: _signedData = SignedDataParser.getInstance(_contentInfo
133: .getContent(DERTags.SEQUENCE));
134: _digests = new HashMap();
135:
136: ASN1SetParser digAlgs = _signedData.getDigestAlgorithms();
137: DEREncodable o;
138:
139: while ((o = digAlgs.readObject()) != null) {
140: AlgorithmIdentifier id = AlgorithmIdentifier
141: .getInstance(o.getDERObject());
142: try {
143: String digestName = CMSSignedHelper.INSTANCE
144: .getDigestAlgName(id.getObjectId()
145: .toString());
146: MessageDigest dig = MessageDigest
147: .getInstance(digestName);
148:
149: this ._digests.put(digestName, dig);
150: } catch (NoSuchAlgorithmException e) {
151: // ignore
152: }
153: }
154:
155: if (_signedContent == null) {
156: //
157: // If the message is simply a certificate chain message getContent() may return null.
158: //
159: ContentInfoParser cont = _signedData
160: .getEncapContentInfo();
161: ASN1OctetStringParser octs = (ASN1OctetStringParser) cont
162: .getContent(DERTags.OCTET_STRING);
163:
164: if (octs != null) {
165: _signedContent = new CMSTypedStream(cont
166: .getContentType().getId(), octs
167: .getOctetStream());
168: }
169: } else {
170: //
171: // content passed in, need to read past empty encapsulated content info object if present
172: //
173: ASN1OctetStringParser octs = (ASN1OctetStringParser) _signedData
174: .getEncapContentInfo().getContent(
175: DERTags.OCTET_STRING);
176:
177: if (octs != null) {
178: InputStream in = octs.getOctetStream();
179:
180: while (in.read() >= 0) {
181: // ignore
182: }
183: }
184: }
185: } catch (IOException e) {
186: throw new CMSException("io exception: " + e.getMessage(), e);
187: }
188:
189: if (_digests.isEmpty()) {
190: throw new CMSException(
191: "no digests could be created for message.");
192: }
193: }
194:
195: /**
196: * Return the version number for the SignedData object
197: *
198: * @return the version number
199: */
200: public int getVersion() {
201: return _signedData.getVersion().getValue().intValue();
202: }
203:
204: /**
205: * return the collection of signers that are associated with the
206: * signatures for the message.
207: * @throws CMSException
208: */
209: public SignerInformationStore getSignerInfos() throws CMSException {
210: if (_signerInfoStore == null) {
211: populateCertCrlSets();
212:
213: List signerInfos = new ArrayList();
214: Map hashes = new HashMap();
215:
216: Iterator it = _digests.keySet().iterator();
217: while (it.hasNext()) {
218: Object digestKey = it.next();
219:
220: hashes.put(digestKey, ((MessageDigest) _digests
221: .get(digestKey)).digest());
222: }
223:
224: try {
225: ASN1SetParser s = _signedData.getSignerInfos();
226: DEREncodable o;
227:
228: while ((o = s.readObject()) != null) {
229: SignerInfo info = SignerInfo.getInstance(o
230: .getDERObject());
231: String digestName = CMSSignedHelper.INSTANCE
232: .getDigestAlgName(info.getDigestAlgorithm()
233: .getObjectId().getId());
234:
235: byte[] hash = (byte[]) hashes.get(digestName);
236:
237: signerInfos.add(new SignerInformation(info,
238: new DERObjectIdentifier(_signedContent
239: .getContentType()), null, hash));
240: }
241: } catch (IOException e) {
242: throw new CMSException("io exception: "
243: + e.getMessage(), e);
244: }
245:
246: _signerInfoStore = new SignerInformationStore(signerInfos);
247: }
248:
249: return _signerInfoStore;
250: }
251:
252: /**
253: * return a X509Store containing the attribute certificates, if any, contained
254: * in this message.
255: *
256: * @param type type of store to create
257: * @param provider provider to use
258: * @return a store of attribute certificates
259: * @exception NoSuchProviderException if the provider requested isn't available.
260: * @exception org.bouncycastle.x509.NoSuchStoreException if the store type isn't available.
261: * @exception CMSException if a general exception prevents creation of the X509Store
262: */
263: public X509Store getAttributeCertificates(String type,
264: String provider) throws NoSuchStoreException,
265: NoSuchProviderException, CMSException {
266: if (_attributeStore == null) {
267: populateCertCrlSets();
268:
269: _attributeStore = HELPER.createAttributeStore(type,
270: provider, _certSet);
271: }
272:
273: return _attributeStore;
274: }
275:
276: /**
277: * return a X509Store containing the public key certificates, if any, contained
278: * in this message.
279: *
280: * @param type type of store to create
281: * @param provider provider to use
282: * @return a store of public key certificates
283: * @exception NoSuchProviderException if the provider requested isn't available.
284: * @exception NoSuchStoreException if the store type isn't available.
285: * @exception CMSException if a general exception prevents creation of the X509Store
286: */
287: public X509Store getCertificates(String type, String provider)
288: throws NoSuchStoreException, NoSuchProviderException,
289: CMSException {
290: if (_certificateStore == null) {
291: populateCertCrlSets();
292:
293: _certificateStore = HELPER.createCertificateStore(type,
294: provider, _certSet);
295: }
296:
297: return _certificateStore;
298: }
299:
300: /**
301: * return a X509Store containing CRLs, if any, contained
302: * in this message.
303: *
304: * @param type type of store to create
305: * @param provider provider to use
306: * @return a store of CRLs
307: * @exception NoSuchProviderException if the provider requested isn't available.
308: * @exception NoSuchStoreException if the store type isn't available.
309: * @exception CMSException if a general exception prevents creation of the X509Store
310: */
311: public X509Store getCRLs(String type, String provider)
312: throws NoSuchStoreException, NoSuchProviderException,
313: CMSException {
314: if (_crlStore == null) {
315: populateCertCrlSets();
316:
317: _crlStore = HELPER.createCRLsStore(type, provider, _crlSet);
318: }
319:
320: return _crlStore;
321: }
322:
323: /**
324: * return a CertStore containing the certificates and CRLs associated with
325: * this message.
326: *
327: * @exception NoSuchProviderException if the provider requested isn't available.
328: * @exception NoSuchAlgorithmException if the cert store isn't available.
329: * @exception CMSException if a general exception prevents creation of the CertStore
330: */
331: public CertStore getCertificatesAndCRLs(String type, String provider)
332: throws NoSuchAlgorithmException, NoSuchProviderException,
333: CMSException {
334: if (_certStore == null) {
335: populateCertCrlSets();
336:
337: _certStore = HELPER.createCertStore(type, provider,
338: _certSet, _crlSet);
339: }
340:
341: return _certStore;
342: }
343:
344: private void populateCertCrlSets() throws CMSException {
345: if (_isCertCrlParsed) {
346: return;
347: }
348:
349: _isCertCrlParsed = true;
350:
351: ASN1SetParser sCerts, sCrls;
352:
353: try {
354: // care! Streaming - these must be done in exactly this order.
355: sCerts = _signedData.getCertificates();
356: if (sCerts != null) {
357: _certSet = ASN1Set.getInstance(sCerts.getDERObject());
358: }
359: sCrls = _signedData.getCrls();
360: if (sCrls != null) {
361: _crlSet = ASN1Set.getInstance(sCrls.getDERObject());
362: }
363: } catch (IOException e) {
364: throw new CMSException("problem parsing cert/crl sets", e);
365: }
366: }
367:
368: public CMSTypedStream getSignedContent() {
369: if (_signedContent != null) {
370: InputStream digStream = _signedContent.getContentStream();
371:
372: Iterator it = _digests.values().iterator();
373:
374: while (it.hasNext()) {
375: digStream = new DigestInputStream(digStream,
376: (MessageDigest) it.next());
377: }
378:
379: return new CMSTypedStream(_signedContent.getContentType(),
380: digStream);
381: } else {
382: return null;
383: }
384: }
385:
386: /**
387: * Replace the signerinformation store associated with the passed
388: * in message contained in the stream original with the new one passed in.
389: * You would probably only want to do this if you wanted to change the unsigned
390: * attributes associated with a signer, or perhaps delete one.
391: * <p>
392: * The output stream is returned unclosed.
393: * </p>
394: * @param original the signed data stream to be used as a base.
395: * @param signerInformationStore the new signer information store to use.
396: * @param out the stream to write the new signed data object to.
397: * @return out.
398: */
399: public static OutputStream replaceSigners(InputStream original,
400: SignerInformationStore signerInformationStore,
401: OutputStream out) throws CMSException, IOException {
402: ASN1StreamParser in = new ASN1StreamParser(original, CMSUtils
403: .getMaximumMemory());
404: ContentInfoParser contentInfo = new ContentInfoParser(
405: (ASN1SequenceParser) in.readObject());
406: SignedDataParser signedData = SignedDataParser
407: .getInstance(contentInfo.getContent(DERTags.SEQUENCE));
408:
409: BERSequenceGenerator sGen = new BERSequenceGenerator(out);
410:
411: sGen.addObject(CMSObjectIdentifiers.signedData);
412:
413: BERSequenceGenerator sigGen = new BERSequenceGenerator(sGen
414: .getRawOutputStream(), 0, true);
415:
416: // version number
417: sigGen.addObject(signedData.getVersion());
418:
419: // digests
420: signedData.getDigestAlgorithms().getDERObject(); // skip old ones
421:
422: ASN1EncodableVector digestAlgs = new ASN1EncodableVector();
423:
424: for (Iterator it = signerInformationStore.getSigners()
425: .iterator(); it.hasNext();) {
426: SignerInformation signer = (SignerInformation) it.next();
427: AlgorithmIdentifier digAlgId;
428:
429: digAlgId = makeAlgId(signer.getDigestAlgOID(), signer
430: .getDigestAlgParams());
431:
432: digestAlgs.add(digAlgId);
433: }
434:
435: sigGen.getRawOutputStream().write(
436: new DERSet(digestAlgs).getEncoded());
437:
438: // encap content info
439: ContentInfoParser encapContentInfo = signedData
440: .getEncapContentInfo();
441:
442: BERSequenceGenerator eiGen = new BERSequenceGenerator(sigGen
443: .getRawOutputStream());
444:
445: eiGen.addObject(encapContentInfo.getContentType());
446:
447: ASN1OctetStringParser octs = (ASN1OctetStringParser) encapContentInfo
448: .getContent(DERTags.OCTET_STRING);
449:
450: if (octs != null) {
451: BEROctetStringGenerator octGen = new BEROctetStringGenerator(
452: eiGen.getRawOutputStream(), 0, true);
453: byte[] inBuffer = new byte[4096];
454: byte[] outBuffer = new byte[4096];
455: InputStream inOctets = octs.getOctetStream();
456: OutputStream outOctets = octGen
457: .getOctetOutputStream(outBuffer);
458:
459: int len;
460: while ((len = inOctets.read(inBuffer, 0, inBuffer.length)) >= 0) {
461: outOctets.write(inBuffer, 0, len);
462: }
463:
464: outOctets.close();
465: }
466:
467: eiGen.close();
468:
469: ASN1SetParser set = signedData.getCertificates();
470:
471: if (set instanceof BERSetParser) {
472: sigGen.getRawOutputStream().write(
473: new BERTaggedObject(false, 0, set.getDERObject())
474: .getEncoded());
475: } else if (set != null) {
476: sigGen.getRawOutputStream().write(
477: new DERTaggedObject(false, 0, set.getDERObject())
478: .getEncoded());
479: }
480:
481: set = signedData.getCrls();
482:
483: if (set instanceof BERSetParser) {
484: sigGen.getRawOutputStream().write(
485: new BERTaggedObject(false, 1, set.getDERObject())
486: .getEncoded());
487: } else if (set != null) {
488: sigGen.getRawOutputStream().write(
489: new DERTaggedObject(false, 1, set.getDERObject())
490: .getEncoded());
491: }
492:
493: ASN1EncodableVector signerInfos = new ASN1EncodableVector();
494: for (Iterator it = signerInformationStore.getSigners()
495: .iterator(); it.hasNext();) {
496: SignerInformation signer = (SignerInformation) it.next();
497:
498: signerInfos.add(signer.toSignerInfo());
499: }
500:
501: sigGen.getRawOutputStream().write(
502: new DERSet(signerInfos).getEncoded());
503:
504: sigGen.close();
505:
506: sGen.close();
507:
508: return out;
509: }
510:
511: /**
512: * Replace the certificate and CRL information associated with this
513: * CMSSignedData object with the new one passed in.
514: * <p>
515: * The output stream is returned unclosed.
516: * </p>
517: * @param original the signed data stream to be used as a base.
518: * @param certsAndCrls the new certificates and CRLs to be used.
519: * @param out the stream to write the new signed data object to.
520: * @return out.
521: * @exception CMSException if there is an error processing the CertStore
522: */
523: public static OutputStream replaceCertificatesAndCRLs(
524: InputStream original, CertStore certsAndCrls,
525: OutputStream out) throws CMSException, IOException {
526: ASN1StreamParser in = new ASN1StreamParser(original, CMSUtils
527: .getMaximumMemory());
528: ContentInfoParser contentInfo = new ContentInfoParser(
529: (ASN1SequenceParser) in.readObject());
530: SignedDataParser signedData = SignedDataParser
531: .getInstance(contentInfo.getContent(DERTags.SEQUENCE));
532:
533: BERSequenceGenerator sGen = new BERSequenceGenerator(out);
534:
535: sGen.addObject(CMSObjectIdentifiers.signedData);
536:
537: BERSequenceGenerator sigGen = new BERSequenceGenerator(sGen
538: .getRawOutputStream(), 0, true);
539:
540: // version number
541: sigGen.addObject(signedData.getVersion());
542:
543: // digests
544: sigGen.getRawOutputStream().write(
545: signedData.getDigestAlgorithms().getDERObject()
546: .getEncoded());
547:
548: // encap content info
549: ContentInfoParser encapContentInfo = signedData
550: .getEncapContentInfo();
551:
552: BERSequenceGenerator eiGen = new BERSequenceGenerator(sigGen
553: .getRawOutputStream());
554:
555: eiGen.addObject(encapContentInfo.getContentType());
556:
557: ASN1OctetStringParser octs = (ASN1OctetStringParser) encapContentInfo
558: .getContent(DERTags.OCTET_STRING);
559:
560: if (octs != null) {
561: BEROctetStringGenerator octGen = new BEROctetStringGenerator(
562: eiGen.getRawOutputStream(), 0, true);
563: byte[] inBuffer = new byte[4096];
564: byte[] outBuffer = new byte[4096];
565: InputStream inOctets = octs.getOctetStream();
566: OutputStream outOctets = octGen
567: .getOctetOutputStream(outBuffer);
568:
569: int len;
570: while ((len = inOctets.read(inBuffer, 0, inBuffer.length)) >= 0) {
571: outOctets.write(inBuffer, 0, len);
572: }
573:
574: outOctets.close();
575: }
576:
577: eiGen.close();
578:
579: //
580: // skip existing certs and CRLs
581: //
582: ASN1SetParser set = signedData.getCertificates();
583:
584: if (set != null) {
585: set.getDERObject();
586: }
587:
588: set = signedData.getCrls();
589:
590: if (set != null) {
591: set.getDERObject();
592: }
593:
594: //
595: // replace the certs and crls in the SignedData object
596: //
597: ASN1Set certs;
598:
599: try {
600: certs = CMSUtils.createBerSetFromList(CMSUtils
601: .getCertificatesFromStore(certsAndCrls));
602: } catch (CertStoreException e) {
603: throw new CMSException(
604: "error getting certs from certStore", e);
605: }
606:
607: if (certs.size() > 0) {
608: sigGen.getRawOutputStream().write(
609: new DERTaggedObject(false, 0, certs).getEncoded());
610: }
611:
612: ASN1Set crls;
613:
614: try {
615: crls = CMSUtils.createBerSetFromList(CMSUtils
616: .getCRLsFromStore(certsAndCrls));
617: } catch (CertStoreException e) {
618: throw new CMSException("error getting crls from certStore",
619: e);
620: }
621:
622: if (crls.size() > 0) {
623: sigGen.getRawOutputStream().write(
624: new DERTaggedObject(false, 1, crls).getEncoded());
625: }
626:
627: sigGen.getRawOutputStream()
628: .write(
629: signedData.getSignerInfos().getDERObject()
630: .getEncoded());
631:
632: sigGen.close();
633:
634: sGen.close();
635:
636: return out;
637: }
638:
639: private static DERObject makeObj(byte[] encoding)
640: throws IOException {
641: if (encoding == null) {
642: return null;
643: }
644:
645: ASN1InputStream aIn = new ASN1InputStream(encoding);
646:
647: return aIn.readObject();
648: }
649:
650: private static AlgorithmIdentifier makeAlgId(String oid,
651: byte[] params) throws IOException {
652: if (params != null) {
653: return new AlgorithmIdentifier(
654: new DERObjectIdentifier(oid), makeObj(params));
655: } else {
656: return new AlgorithmIdentifier(
657: new DERObjectIdentifier(oid), new DERNull());
658: }
659: }
660: }
|