001: package org.bouncycastle.mail.smime;
002:
003: import org.bouncycastle.asn1.cms.IssuerAndSerialNumber;
004: import org.bouncycastle.cms.CMSTypedStream;
005: import org.bouncycastle.jce.PrincipalUtil;
006: import org.bouncycastle.mail.smime.util.CRLFOutputStream;
007: import org.bouncycastle.mail.smime.util.FileBackedMimeBodyPart;
008:
009: import javax.mail.BodyPart;
010: import javax.mail.MessagingException;
011: import javax.mail.internet.ContentType;
012: import javax.mail.internet.MimeBodyPart;
013: import javax.mail.internet.MimeMultipart;
014: import java.io.ByteArrayInputStream;
015: import java.io.File;
016: import java.io.FilterOutputStream;
017: import java.io.IOException;
018: import java.io.InputStream;
019: import java.io.OutputStream;
020: import java.security.cert.CertificateParsingException;
021: import java.security.cert.X509Certificate;
022: import java.util.Enumeration;
023:
024: public class SMIMEUtil {
025: private static final int BUF_SIZE = 32760;
026:
027: static boolean isCanonicalisationRequired(MimeBodyPart bodyPart,
028: String defaultContentTransferEncoding)
029: throws MessagingException {
030: String[] cte = bodyPart.getHeader("Content-Transfer-Encoding");
031: String contentTransferEncoding;
032:
033: if (cte == null) {
034: contentTransferEncoding = defaultContentTransferEncoding;
035: } else {
036: contentTransferEncoding = cte[0];
037: }
038:
039: return !contentTransferEncoding.equalsIgnoreCase("binary");
040: }
041:
042: static class LineOutputStream extends FilterOutputStream {
043: private static byte newline[];
044:
045: public LineOutputStream(OutputStream outputstream) {
046: super (outputstream);
047: }
048:
049: public void writeln(String s) throws MessagingException {
050: try {
051: byte abyte0[] = getBytes(s);
052: super .out.write(abyte0);
053: super .out.write(newline);
054: } catch (Exception exception) {
055: throw new MessagingException("IOException", exception);
056: }
057: }
058:
059: public void writeln() throws MessagingException {
060: try {
061: super .out.write(newline);
062: } catch (Exception exception) {
063: throw new MessagingException("IOException", exception);
064: }
065: }
066:
067: static {
068: newline = new byte[2];
069: newline[0] = 13;
070: newline[1] = 10;
071: }
072:
073: private static byte[] getBytes(String s) {
074: char ac[] = s.toCharArray();
075: int i = ac.length;
076: byte abyte0[] = new byte[i];
077: int j = 0;
078:
079: while (j < i) {
080: abyte0[j] = (byte) ac[j++];
081: }
082:
083: return abyte0;
084: }
085: }
086:
087: /**
088: * internal preamble is generally included in signatures, while this is technically wrong,
089: * if we find internal preamble we include it by default.
090: */
091: static void outputPreamble(LineOutputStream lOut,
092: MimeBodyPart part, String boundary)
093: throws MessagingException, IOException {
094: InputStream in;
095:
096: try {
097: in = part.getRawInputStream();
098: } catch (MessagingException e) {
099: return; // no underlying content rely on default generation
100: }
101:
102: String line;
103:
104: while ((line = readLine(in)) != null) {
105: if (line.equals(boundary)) {
106: break;
107: }
108:
109: lOut.writeln(line);
110: }
111:
112: in.close();
113:
114: if (line == null) {
115: throw new MessagingException("no boundary found");
116: }
117: }
118:
119: /*
120: * read a line of input stripping of the tailing \r\n
121: */
122: private static String readLine(InputStream in) throws IOException {
123: StringBuffer b = new StringBuffer();
124:
125: int ch;
126: while ((ch = in.read()) >= 0 && ch != '\n') {
127: if (ch != '\r') {
128: b.append((char) ch);
129: }
130: }
131:
132: if (ch < 0) {
133: return null;
134: }
135:
136: return b.toString();
137: }
138:
139: static void outputBodyPart(OutputStream out, BodyPart bodyPart,
140: String defaultContentTransferEncoding)
141: throws MessagingException, IOException {
142: if (bodyPart instanceof MimeBodyPart) {
143: MimeBodyPart mimePart = (MimeBodyPart) bodyPart;
144: String[] cte = mimePart
145: .getHeader("Content-Transfer-Encoding");
146: String contentTransferEncoding;
147:
148: if (mimePart.getContent() instanceof MimeMultipart) {
149: MimeMultipart mp = (MimeMultipart) bodyPart
150: .getContent();
151: ContentType contentType = new ContentType(mp
152: .getContentType());
153: String boundary = "--"
154: + contentType.getParameter("boundary");
155:
156: SMIMEUtil.LineOutputStream lOut = new SMIMEUtil.LineOutputStream(
157: out);
158:
159: Enumeration headers = mimePart.getAllHeaderLines();
160: while (headers.hasMoreElements()) {
161: lOut.writeln((String) headers.nextElement());
162: }
163:
164: lOut.writeln(); // CRLF separator
165:
166: outputPreamble(lOut, mimePart, boundary);
167:
168: for (int i = 0; i < mp.getCount(); i++) {
169: lOut.writeln(boundary);
170: outputBodyPart(out, mp.getBodyPart(i),
171: defaultContentTransferEncoding);
172: lOut.writeln(); // CRLF terminator
173: }
174:
175: lOut.writeln(boundary + "--");
176: return;
177: }
178:
179: if (cte == null) {
180: contentTransferEncoding = defaultContentTransferEncoding;
181: } else {
182: contentTransferEncoding = cte[0];
183: }
184:
185: if (!contentTransferEncoding.equalsIgnoreCase("base64")
186: && !contentTransferEncoding
187: .equalsIgnoreCase("quoted-printable")) {
188: if (!contentTransferEncoding.equalsIgnoreCase("binary")) {
189: out = new CRLFOutputStream(out);
190: }
191: bodyPart.writeTo(out);
192: out.flush();
193: return;
194: }
195:
196: //
197: // Special handling for Base64 or quoted-printable encoded
198: // body part - this is to get around JavaMail's habit of
199: // decoding and then re-encoding base64 data...
200: //
201:
202: //
203: // Write headers
204: //
205: LineOutputStream outLine = new LineOutputStream(out);
206: for (Enumeration e = mimePart.getAllHeaderLines(); e
207: .hasMoreElements();) {
208: outLine.writeln((String) e.nextElement());
209: }
210:
211: outLine.writeln();
212: outLine.flush();
213:
214: //
215: // Write raw content, performing canonicalization
216: //
217: InputStream inRaw = mimePart.getRawInputStream();
218: CRLFOutputStream outCRLF = new CRLFOutputStream(out);
219:
220: byte[] buf = new byte[BUF_SIZE];
221:
222: int len;
223: while ((len = inRaw.read(buf, 0, buf.length)) > 0) {
224: outCRLF.write(buf, 0, len);
225: }
226:
227: outCRLF.flush();
228: } else {
229: if (!defaultContentTransferEncoding
230: .equalsIgnoreCase("binary")) {
231: out = new CRLFOutputStream(out);
232: }
233:
234: bodyPart.writeTo(new CRLFOutputStream(out));
235: }
236: }
237:
238: /**
239: * return the MimeBodyPart described in the raw bytes provided in content
240: */
241: public static MimeBodyPart toMimeBodyPart(byte[] content)
242: throws SMIMEException {
243: return toMimeBodyPart(new ByteArrayInputStream(content));
244: }
245:
246: /**
247: * return the MimeBodyPart described in the input stream content
248: */
249: public static MimeBodyPart toMimeBodyPart(InputStream content)
250: throws SMIMEException {
251: try {
252: return new MimeBodyPart(content);
253: } catch (MessagingException e) {
254: throw new SMIMEException("exception creating body part.", e);
255: }
256: }
257:
258: /**
259: * return a file backed MimeBodyPart described in {@link CMSTypedStream} content.
260: * </p>
261: */
262: public static FileBackedMimeBodyPart toMimeBodyPart(
263: CMSTypedStream content) throws SMIMEException {
264: try {
265: return toMimeBodyPart(content, File.createTempFile(
266: "bcMail", ".mime"));
267: } catch (IOException e) {
268: throw new SMIMEException("IOException creating tmp file:"
269: + e.getMessage());
270: }
271: }
272:
273: /**
274: * Return a file based MimeBodyPart represented by content and backed
275: * by the file represented by file.
276: *
277: * @param content content stream containing body part.
278: * @param file file to store the decoded body part in.
279: * @return the decoded body part.
280: * @throws SMIMEException
281: */
282: public static FileBackedMimeBodyPart toMimeBodyPart(
283: CMSTypedStream content, File file) throws SMIMEException {
284: try {
285: return new FileBackedMimeBodyPart(content
286: .getContentStream(), file);
287: } catch (IOException e) {
288: throw new SMIMEException(
289: "can't save content to file: " + e, e);
290: } catch (MessagingException e) {
291: throw new SMIMEException("can't create part: " + e, e);
292: }
293: }
294:
295: /**
296: * Return a CMS IssuerAndSerialNumber structure for the passed in X.509 certificate.
297: *
298: * @param cert the X.509 certificate to get the issuer and serial number for.
299: * @return an IssuerAndSerialNumber structure representing the certificate.
300: */
301: public static IssuerAndSerialNumber createIssuerAndSerialNumberFor(
302: X509Certificate cert) throws CertificateParsingException {
303: try {
304: return new IssuerAndSerialNumber(PrincipalUtil
305: .getIssuerX509Principal(cert), cert
306: .getSerialNumber());
307: } catch (Exception e) {
308: throw new CertificateParsingException(
309: "exception extracting issuer and serial number: "
310: + e);
311: }
312: }
313: }
|