Java tutorial
/* * Copyright (c) 2008-2011, Martijn Brinkers, Djigzo. * * This file is part of Djigzo email encryption. * * Djigzo is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License * version 3, 19 November 2007 as published by the Free Software * Foundation. * * Djigzo is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public * License along with Djigzo. If not, see <http://www.gnu.org/licenses/> * * Additional permission under GNU AGPL version 3 section 7 * * If you modify this Program, or any covered work, by linking or * combining it with aspectjrt.jar, aspectjweaver.jar, tyrex-1.0.3.jar, * freemarker.jar, dom4j.jar, mx4j-jmx.jar, mx4j-tools.jar, * spice-classman-1.0.jar, spice-loggerstore-0.5.jar, spice-salt-0.8.jar, * spice-xmlpolicy-1.0.jar, saaj-api-1.3.jar, saaj-impl-1.3.jar, * wsdl4j-1.6.1.jar (or modified versions of these libraries), * containing parts covered by the terms of Eclipse Public License, * tyrex license, freemarker license, dom4j license, mx4j license, * Spice Software License, Common Development and Distribution License * (CDDL), Common Public License (CPL) the licensors of this Program grant * you additional permission to convey the resulting work. */ package mitm.common.security.smime; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.Enumeration; import javax.mail.BodyPart; import javax.mail.MessagingException; import javax.mail.Multipart; import javax.mail.Part; import javax.mail.internet.MimeBodyPart; import mitm.common.mail.MailUtils; import mitm.common.security.cms.CMSContentType; import mitm.common.security.cms.CMSContentTypeClassifier; import mitm.common.util.MiscStringUtils; import org.apache.commons.io.IOUtils; import org.bouncycastle.mail.smime.util.CRLFOutputStream; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class SMIMEUtils { private static Logger logger = LoggerFactory.getLogger(SMIMEUtils.class); /** * Split up the multipart in a message part and signed part. Returns null if the message * does not contain one of these parts or if it contains more or less than 2 parts. * @param multiPart * @return first item is the message part, second the signature part * @throws MessagingException */ public static BodyPart[] dissectSigned(Multipart multipart) throws MessagingException { BodyPart messagePart = null; BodyPart signaturePart = null; if (multipart == null) { return null; } if (multipart.getCount() != 2) { logger.debug("Multipart does not contain 2 parts."); return null; } for (int i = 0; i < 2; i++) { BodyPart part = multipart.getBodyPart(i); /* * Check if we have found the signature part. We need to make sure that the content-type * is not multipart/signed because that fails when we have a clear signed message that * is signed again. */ if (SMIMEHeader.getSMIMEContentType(part) == SMIMEHeader.Type.CLEAR_SIGNED && !part.isMimeType("multipart/signed")) { signaturePart = part; } else { messagePart = part; } } if (messagePart == null || signaturePart == null) { logger.debug("Multipart does not contain a message and signature part."); return null; } return new BodyPart[] { messagePart, signaturePart }; } /** * Returns the S/MIME type of the message (if S/MIME). It first checks the message headers to see if the * message can be a S/MIME message. If so it checks if it's a clear signed message by checking the * headers further. If it's not a clear signed message it will try to determine the CMS content type. */ public static SMIMEType getSMIMEType(Part part) throws MessagingException, IOException { /* first check the headers */ SMIMEHeader.Type headerType = SMIMEHeader.getSMIMEContentType(part); if (headerType == SMIMEHeader.Type.NO_SMIME) { return SMIMEType.NONE; } if (headerType == SMIMEHeader.Type.CLEAR_SIGNED || headerType == SMIMEHeader.Type.UNKNOWN_CLEAR_SIGNED) { Object content = part.getContent(); if (!(content instanceof Multipart)) { logger.warn("Header says clear signed but content is not multipart."); return SMIMEType.NONE; } Multipart multipart = (Multipart) content; /* the content should be a multipart/mixed with 2 parts */ BodyPart[] parts = SMIMEUtils.dissectSigned(multipart); if (parts == null) { if (headerType == SMIMEHeader.Type.CLEAR_SIGNED) { logger.warn("Header says s/mime but missing message part and/or signature part."); } else { logger.debug("Message is clear signed but not S/MIME signed."); } return SMIMEType.NONE; } CMSContentType cmsType = CMSContentTypeClassifier.getContentType(parts[1].getInputStream()); /* check if the signed part is really a CMS structure */ if (cmsType != CMSContentType.SIGNEDDATA) { logger.warn("Header says s/mime but signature part is not a valid CMS signed data."); return SMIMEType.NONE; } return SMIMEType.SIGNED; } else { /* check the CMS structure */ CMSContentType cmsType = CMSContentTypeClassifier.getContentType(part.getInputStream()); switch (cmsType) { case SIGNEDDATA: return SMIMEType.SIGNED; case ENVELOPEDDATA: return SMIMEType.ENCRYPTED; case COMPRESSEDDATA: return SMIMEType.COMPRESSED; default: break; } logger.warn("Header says s/mime but the message is not a valid CMS structure."); } return SMIMEType.NONE; } public static void writeBodyPart(BodyPart bodyPart, OutputStream output, String defaultContentTransferEncoding) throws IOException, MessagingException { if (bodyPart instanceof MimeBodyPart) { MimeBodyPart mimeBodyPart = (MimeBodyPart) bodyPart; String[] contentTransferEncodings = bodyPart.getHeader("Content-Transfer-Encoding"); String contentTransferEncoding = defaultContentTransferEncoding; if (contentTransferEncodings != null && contentTransferEncodings.length > 0) { contentTransferEncoding = contentTransferEncodings[0]; } /* * First try the raw input stream. * If message is created from a stream Javamail will return the raw stream. If * the message is created from 'scratch' getRawInputStream throws a * MessagingException. We will therefore first try the raw version and if * that fails we fallback on writeTo. */ try { InputStream input = mimeBodyPart.getRawInputStream(); Enumeration<?> lines = mimeBodyPart.getAllHeaderLines(); /* step through all header lines */ while (lines.hasMoreElements()) { String header = (String) lines.nextElement(); output.write(MiscStringUtils.toAsciiBytes(header)); output.write(MailUtils.CRLF_BYTES); } output.write(MailUtils.CRLF_BYTES); if (!contentTransferEncoding.equalsIgnoreCase("binary")) { output = new CRLFOutputStream(output); } IOUtils.copy(input, output); output.flush(); } catch (MessagingException e) { /* * Fallback to writeTo */ if (!contentTransferEncoding.equalsIgnoreCase("binary")) { output = new CRLFOutputStream(output); } bodyPart.writeTo(output); output.flush(); } } else { if (!defaultContentTransferEncoding.equalsIgnoreCase("binary")) { output = new CRLFOutputStream(output); } bodyPart.writeTo(output); output.flush(); } } }