001: package org.bouncycastle.mail.smime;
002:
003: import org.bouncycastle.cms.CMSException;
004: import org.bouncycastle.cms.CMSSignedDataParser;
005: import org.bouncycastle.cms.CMSTypedStream;
006:
007: import javax.activation.CommandMap;
008: import javax.activation.MailcapCommandMap;
009: import javax.mail.BodyPart;
010: import javax.mail.MessagingException;
011: import javax.mail.Part;
012: import javax.mail.Session;
013: import javax.mail.internet.MimeBodyPart;
014: import javax.mail.internet.MimeMessage;
015: import javax.mail.internet.MimeMultipart;
016: import java.io.BufferedInputStream;
017: import java.io.BufferedOutputStream;
018: import java.io.File;
019: import java.io.FileInputStream;
020: import java.io.FileNotFoundException;
021: import java.io.FileOutputStream;
022: import java.io.IOException;
023: import java.io.InputStream;
024: import java.io.OutputStream;
025:
026: /**
027: * general class for handling a pkcs7-signature message.
028: * <p>
029: * A simple example of usage - note, in the example below the validity of
030: * the certificate isn't verified, just the fact that one of the certs
031: * matches the given signer...
032: * <p>
033: * <pre>
034: * CertStore certs = s.getCertificates("Collection", "BC");
035: * SignerInformationStore signers = s.getSignerInfos();
036: * Collection c = signers.getSigners();
037: * Iterator it = c.iterator();
038: *
039: * while (it.hasNext())
040: * {
041: * SignerInformation signer = (SignerInformation)it.next();
042: * Collection certCollection = certs.getCertificates(signer.getSID());
043: *
044: * Iterator certIt = certCollection.iterator();
045: * X509Certificate cert = (X509Certificate)certIt.next();
046: *
047: * if (signer.verify(cert.getPublicKey()))
048: * {
049: * verified++;
050: * }
051: * }
052: * </pre>
053: * <p>
054: * Note: if you are using this class with AS2 or some other protocol
055: * that does not use 7bit as the default content transfer encoding you
056: * will need to use the constructor that allows you to specify the default
057: * content transfer encoding, such as "binary".
058: * </p>
059: */
060: public class SMIMESignedParser extends CMSSignedDataParser {
061: Object message;
062: MimeBodyPart content;
063:
064: private static InputStream getInputStream(Part bodyPart)
065: throws MessagingException {
066: try {
067: if (bodyPart.isMimeType("multipart/signed")) {
068: throw new MessagingException(
069: "attempt to create signed data object from multipart content - use MimeMultipart constructor.");
070: }
071:
072: return bodyPart.getInputStream();
073: } catch (IOException e) {
074: throw new MessagingException("can't extract input stream: "
075: + e);
076: }
077: }
078:
079: private static File getTmpFile() throws MessagingException {
080: try {
081: return File.createTempFile("bcMail", ".mime");
082: } catch (IOException e) {
083: throw new MessagingException("can't extract input stream: "
084: + e);
085: }
086: }
087:
088: private static CMSTypedStream getSignedInputStream(
089: BodyPart bodyPart, String defaultContentTransferEncoding,
090: File backingFile) throws MessagingException {
091: try {
092: OutputStream out = new BufferedOutputStream(
093: new FileOutputStream(backingFile));
094:
095: SMIMEUtil.outputBodyPart(out, bodyPart,
096: defaultContentTransferEncoding);
097:
098: out.close();
099:
100: InputStream in = new TemporaryFileInputStream(backingFile);
101:
102: return new CMSTypedStream(in);
103: } catch (IOException e) {
104: throw new MessagingException("can't extract input stream: "
105: + e);
106: }
107: }
108:
109: static {
110: MailcapCommandMap mc = (MailcapCommandMap) CommandMap
111: .getDefaultCommandMap();
112:
113: mc
114: .addMailcap("application/pkcs7-signature;; x-java-content-handler=org.bouncycastle.mail.smime.handlers.pkcs7_signature");
115: mc
116: .addMailcap("application/pkcs7-mime;; x-java-content-handler=org.bouncycastle.mail.smime.handlers.pkcs7_mime");
117: mc
118: .addMailcap("application/x-pkcs7-signature;; x-java-content-handler=org.bouncycastle.mail.smime.handlers.x_pkcs7_signature");
119: mc
120: .addMailcap("application/x-pkcs7-mime;; x-java-content-handler=org.bouncycastle.mail.smime.handlers.x_pkcs7_mime");
121: mc
122: .addMailcap("multipart/signed;; x-java-content-handler=org.bouncycastle.mail.smime.handlers.multipart_signed");
123:
124: CommandMap.setDefaultCommandMap(mc);
125: }
126:
127: /**
128: * base constructor using a defaultContentTransferEncoding of 7bit. A temporary backing file
129: * will be created for the signed data.
130: *
131: * @param message signed message with signature.
132: * @exception MessagingException on an error extracting the signature or
133: * otherwise processing the message.
134: * @exception CMSException if some other problem occurs.
135: */
136: public SMIMESignedParser(MimeMultipart message)
137: throws MessagingException, CMSException {
138: this (message, getTmpFile());
139: }
140:
141: /**
142: * base constructor using a defaultContentTransferEncoding of 7bit and a specified backing file.
143: *
144: * @param message signed message with signature.
145: * @param backingFile the temporary file to use to back the signed data.
146: * @exception MessagingException on an error extracting the signature or
147: * otherwise processing the message.
148: * @exception CMSException if some other problem occurs.
149: */
150: public SMIMESignedParser(MimeMultipart message, File backingFile)
151: throws MessagingException, CMSException {
152: this (message, "7bit", backingFile);
153: }
154:
155: /**
156: * base constructor with settable contentTransferEncoding. A temporary backing file will be created
157: * to contain the signed data.
158: *
159: * @param message the signed message with signature.
160: * @param defaultContentTransferEncoding new default to use.
161: * @exception MessagingException on an error extracting the signature or
162: * otherwise processing the message.
163: * @exception CMSException if some other problem occurs.
164: */
165: public SMIMESignedParser(MimeMultipart message,
166: String defaultContentTransferEncoding)
167: throws MessagingException, CMSException {
168: this (message, defaultContentTransferEncoding, getTmpFile());
169: }
170:
171: /**
172: * base constructor with settable contentTransferEncoding and a specified backing file.
173: *
174: * @param message the signed message with signature.
175: * @param defaultContentTransferEncoding new default to use.
176: * @param backingFile the temporary file to use to back the signed data.
177: * @exception MessagingException on an error extracting the signature or
178: * otherwise processing the message.
179: * @exception CMSException if some other problem occurs.
180: */
181: public SMIMESignedParser(MimeMultipart message,
182: String defaultContentTransferEncoding, File backingFile)
183: throws MessagingException, CMSException {
184: super (getSignedInputStream(message.getBodyPart(0),
185: defaultContentTransferEncoding, backingFile),
186: getInputStream(message.getBodyPart(1)));
187:
188: this .message = message;
189: this .content = (MimeBodyPart) message.getBodyPart(0);
190:
191: drainContent();
192: }
193:
194: /**
195: * base constructor for a signed message with encapsulated content.
196: * <p>
197: * Note: in this case the encapsulated MimeBody part will only be suitable for a single
198: * writeTo - once writeTo has been called the file containing the body part will be deleted.
199: * </p>
200: * @param message the message containing the encapsulated signed data.
201: * @exception MessagingException on an error extracting the signature or
202: * otherwise processing the message.
203: * @exception SMIMEException if the body part encapsulated in the message cannot be extracted.
204: * @exception CMSException if some other problem occurs.
205: */
206: public SMIMESignedParser(Part message) throws MessagingException,
207: CMSException, SMIMEException {
208: super (getInputStream(message));
209:
210: this .message = message;
211:
212: CMSTypedStream cont = this .getSignedContent();
213:
214: if (cont != null) {
215: this .content = SMIMEUtil.toMimeBodyPart(cont);
216: }
217: }
218:
219: /**
220: * Constructor for a signed message with encapsulated content. The encapsulated
221: * content, if it exists, is written to the file represented by the File object
222: * passed in.
223: *
224: * @param message the Part containing the signed content.
225: * @param file the file the encapsulated part is to be written to after it has been decoded.
226: *
227: * @exception MessagingException on an error extracting the signature or
228: * otherwise processing the message.
229: * @exception SMIMEException if the body part encapsulated in the message cannot be extracted.
230: * @exception CMSException if some other problem occurs.
231: */
232: public SMIMESignedParser(Part message, File file)
233: throws MessagingException, CMSException, SMIMEException {
234: super (getInputStream(message));
235:
236: this .message = message;
237:
238: CMSTypedStream cont = this .getSignedContent();
239:
240: if (cont != null) {
241: this .content = SMIMEUtil.toMimeBodyPart(cont, file);
242: }
243: }
244:
245: /**
246: * return the content that was signed.
247: * @return the signed body part in this message.
248: */
249: public MimeBodyPart getContent() {
250: return content;
251: }
252:
253: /**
254: * Return the content that was signed as a mime message.
255: *
256: * @param session the session to base the MimeMessage around.
257: * @return a MimeMessage holding the content.
258: * @throws MessagingException if there is an issue creating the MimeMessage.
259: * @throws IOException if there is an issue reading the content.
260: */
261: public MimeMessage getContentAsMimeMessage(Session session)
262: throws MessagingException, IOException {
263: return new MimeMessage(session, getSignedContent()
264: .getContentStream());
265: }
266:
267: /**
268: * return the content that was signed with its signature attached.
269: * @return depending on whether this was unencapsulated or not it will return a MimeMultipart
270: * or a MimeBodyPart
271: */
272: public Object getContentWithSignature() {
273: return message;
274: }
275:
276: private void drainContent() throws CMSException {
277: try {
278: this .getSignedContent().drain();
279: } catch (IOException e) {
280: throw new CMSException(
281: "unable to read content for verification: " + e, e);
282: }
283: }
284:
285: private static class TemporaryFileInputStream extends
286: BufferedInputStream {
287: private final File _file;
288:
289: TemporaryFileInputStream(File file)
290: throws FileNotFoundException {
291: super (new FileInputStream(file));
292:
293: _file = file;
294: }
295:
296: public void close() throws IOException {
297: super.close();
298:
299: _file.delete();
300: }
301: }
302: }
|