mitm.common.security.smime.SMIMEUtils.java Source code

Java tutorial

Introduction

Here is the source code for mitm.common.security.smime.SMIMEUtils.java

Source

/*
 * 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();
        }
    }
}