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

Java tutorial

Introduction

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

Source

/*
 * Copyright (c) 2008-2012, 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.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.CertificateEncodingException;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.Collection;

import javax.crypto.SecretKey;
import javax.mail.BodyPart;
import javax.mail.MessagingException;
import javax.mail.Multipart;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMessage;

import mitm.common.mail.BodyPartUtils;
import mitm.common.mail.HeaderUtils;
import mitm.common.mail.MailSession;
import mitm.common.mail.matcher.ContentHeaderNameMatcher;
import mitm.common.mail.matcher.HeaderMatcher;
import mitm.common.mail.matcher.NotHeaderNameMatcher;
import mitm.common.mail.matcher.ProtectedContentHeaderNameMatcher;
import mitm.common.security.SecurityFactory;
import mitm.common.security.SecurityFactoryFactory;
import mitm.common.security.bouncycastle.X509CertificateHolderStore;
import mitm.common.security.certificate.X509CertificateInspector;
import mitm.common.util.Check;

import org.bouncycastle.asn1.cms.AttributeTable;
import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder;
import org.bouncycastle.cms.CMSException;
import org.bouncycastle.cms.DefaultSignedAttributeTableGenerator;
import org.bouncycastle.cms.SignerInfoGenerator;
import org.bouncycastle.cms.SignerInfoGeneratorBuilder;
import org.bouncycastle.cms.SimpleAttributeTableGenerator;
import org.bouncycastle.cms.jcajce.JceCMSContentEncryptorBuilder;
import org.bouncycastle.cms.jcajce.JceKEKRecipientInfoGenerator;
import org.bouncycastle.cms.jcajce.JceKeyTransRecipientInfoGenerator;
import org.bouncycastle.mail.smime.SMIMECompressedGenerator;
import org.bouncycastle.mail.smime.SMIMEEnvelopedGenerator;
import org.bouncycastle.mail.smime.SMIMEException;
import org.bouncycastle.mail.smime.SMIMESignedGenerator;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder;

/**
 * 
 * This class is not thread safe.
 * 
 * @author Martijn Brinkers
 *
 */
public class SMIMEBuilderImpl implements SMIMEBuilder {
    /*
     * The source mime message
     */
    private final MimeMessage sourceMessage;

    /*
     * header name matcher uses to copy header names from source part to destination part 
     */
    private final HeaderMatcher protectedContentMatcher;

    /*
     * contains the active body part
     */
    private MimeBodyPart bodyPart;

    /* 
     * generator for encrypting messages
     */
    private SMIMEEnvelopedGenerator envelopedGenerator;

    /* 
     * generator for signing of messages
     */
    private SMIMESignedGenerator signedGenerator;

    /*
     * Generator for compression of messages
     */
    private SMIMECompressedGenerator compressedGenerator;

    /*
     * If true the old x-pkcs7-* content types will be used
     */
    private boolean useDeprecatedContentTypes;

    /*
     * The provider for non sensitive operations
     */
    private String nonSensitiveProvider;

    /*
     * The provider for sensitive operations
     */
    private String sensitiveProvider;

    public SMIMEBuilderImpl(MimeMessage message) throws MessagingException, IOException {
        this(message, ProtectedContentHeaderNameMatcher.DEFAULT_PROTECTED_CONTENT_HEADERS);
    }

    public SMIMEBuilderImpl(MimeMessage message, String... protectedHeaders)
            throws MessagingException, IOException {
        this.protectedContentMatcher = new ProtectedContentHeaderNameMatcher(protectedHeaders);

        this.sourceMessage = message;

        SecurityFactory securityFactory = SecurityFactoryFactory.getSecurityFactory();

        this.nonSensitiveProvider = securityFactory.getNonSensitiveProvider();
        this.sensitiveProvider = securityFactory.getSensitiveProvider();

        this.bodyPart = BodyPartUtils.makeContentBodyPart(message, protectedContentMatcher);

        this.envelopedGenerator = new PrivateSMIMEEnvelopedGenerator();
        this.signedGenerator = new PrivateSMIMESignedGenerator();
        this.compressedGenerator = new PrivateSMIMECompressedGenerator();
    }

    @Override
    public void addCertificates(Collection<X509Certificate> certificates) throws SMIMEBuilderException {
        if (certificates == null) {
            return;
        }

        signedGenerator.addCertificates(new X509CertificateHolderStore(certificates));
    }

    @Override
    public void addCertificates(X509Certificate... certificates) throws SMIMEBuilderException {
        if (certificates == null) {
            return;
        }

        addCertificates(Arrays.asList(certificates));
    }

    @Override
    public void addRecipient(X509Certificate certificate, SMIMERecipientMode mode) throws SMIMEBuilderException {
        Check.notNull(certificate, "certificate");

        try {
            byte[] subjectKeyIdentifier = X509CertificateInspector.getSubjectKeyIdentifier(certificate);

            if ((mode == SMIMERecipientMode.SUBJECT_KEY_ID_IF_AVAILABLE || mode == SMIMERecipientMode.BOTH)
                    && subjectKeyIdentifier != null) {
                PublicKey publicKey = certificate.getPublicKey();

                JceKeyTransRecipientInfoGenerator recipientInfoGenerator = new JceKeyTransRecipientInfoGenerator(
                        subjectKeyIdentifier, publicKey);

                recipientInfoGenerator.setProvider(nonSensitiveProvider);

                envelopedGenerator.addRecipientInfoGenerator(recipientInfoGenerator);
            }

            if (mode == SMIMERecipientMode.ISSUER_SERIAL || mode == SMIMERecipientMode.BOTH
                    || subjectKeyIdentifier == null) {
                JceKeyTransRecipientInfoGenerator recipientInfoGenerator = new JceKeyTransRecipientInfoGenerator(
                        certificate);

                recipientInfoGenerator.setProvider(nonSensitiveProvider);

                envelopedGenerator.addRecipientInfoGenerator(recipientInfoGenerator);
            }
        } catch (IOException e) {
            throw new SMIMEBuilderException(e);
        } catch (CertificateEncodingException e) {
            throw new SMIMEBuilderException(e);
        }
    }

    @Override
    public void addRecipient(SecretKey secretKey, byte[] keyIdentifier) {
        JceKEKRecipientInfoGenerator recipientInfoGenerator = new JceKEKRecipientInfoGenerator(keyIdentifier,
                secretKey);

        envelopedGenerator.addRecipientInfoGenerator(recipientInfoGenerator);
    }

    private void addSigner(PrivateKey privateKey, X509Certificate signer, SMIMESigningAlgorithm algorithm,
            AttributeTable signedAttr, AttributeTable unsignedAttr) throws SMIMEBuilderException {
        try {
            JcaDigestCalculatorProviderBuilder digestBuilder = new JcaDigestCalculatorProviderBuilder();

            digestBuilder.setProvider(nonSensitiveProvider);

            SignerInfoGeneratorBuilder signerInfoBuilder = new SignerInfoGeneratorBuilder(digestBuilder.build());

            if (signedAttr != null) {
                signerInfoBuilder.setSignedAttributeGenerator(new DefaultSignedAttributeTableGenerator(signedAttr));
            }

            if (unsignedAttr != null) {
                signerInfoBuilder.setUnsignedAttributeGenerator(new SimpleAttributeTableGenerator(unsignedAttr));
            }

            JcaContentSignerBuilder contentSignerBuilder = new JcaContentSignerBuilder(algorithm.getAlgorithm());

            contentSignerBuilder.setProvider(sensitiveProvider);

            SignerInfoGenerator signerInfoGenerator = signerInfoBuilder
                    .build(contentSignerBuilder.build(privateKey), new JcaX509CertificateHolder(signer));

            signedGenerator.addSignerInfoGenerator(signerInfoGenerator);
        } catch (OperatorCreationException e) {
            throw new SMIMEBuilderException(e);
        } catch (CertificateEncodingException e) {
            throw new SMIMEBuilderException(e);
        }
    }

    @Override
    public void addSigner(PrivateKey privateKey, X509Certificate signer, SMIMESigningAlgorithm algorithm)
            throws SMIMEBuilderException {
        try {
            addSigner(privateKey, signer, algorithm, SMIMEAttributeUtils.getDefaultSignedAttributes(), null);
        } catch (IOException e) {
            throw new SMIMEBuilderException(e);
        }
    }

    @Override
    public void addSigner(PrivateKey privateKey, X509Certificate signer, SMIMESigningAlgorithm algorithm,
            X509Certificate encryptionKeyPreference) throws SMIMEBuilderException {
        try {
            addSigner(privateKey, signer, algorithm,
                    SMIMEAttributeUtils.getDefaultSignedAttributes(encryptionKeyPreference), null);
        } catch (IOException e) {
            throw new SMIMEBuilderException(e);
        }
    }

    public void addSigner(PrivateKey privateKey, byte[] subjectKeyIdentifier, SMIMESigningAlgorithm algorithm,
            AttributeTable signedAttr, AttributeTable unsignedAttr) throws SMIMEBuilderException {
        try {
            JcaDigestCalculatorProviderBuilder digestBuilder = new JcaDigestCalculatorProviderBuilder();

            digestBuilder.setProvider(nonSensitiveProvider);

            SignerInfoGeneratorBuilder signerInfoBuilder = new SignerInfoGeneratorBuilder(digestBuilder.build());

            if (signedAttr != null) {
                signerInfoBuilder.setSignedAttributeGenerator(new DefaultSignedAttributeTableGenerator(signedAttr));
            }

            if (unsignedAttr != null) {
                signerInfoBuilder.setUnsignedAttributeGenerator(new SimpleAttributeTableGenerator(unsignedAttr));
            }

            JcaContentSignerBuilder contentSignerBuilder = new JcaContentSignerBuilder(algorithm.getAlgorithm());

            contentSignerBuilder.setProvider(sensitiveProvider);

            SignerInfoGenerator signerInfoGenerator = signerInfoBuilder
                    .build(contentSignerBuilder.build(privateKey), subjectKeyIdentifier);

            signedGenerator.addSignerInfoGenerator(signerInfoGenerator);
        } catch (OperatorCreationException e) {
            throw new SMIMEBuilderException(e);
        }
    }

    @Override
    public void addSigner(PrivateKey privateKey, byte[] subjectKeyIdentifier, SMIMESigningAlgorithm algorithm)
            throws SMIMEBuilderException {
        try {
            addSigner(privateKey, subjectKeyIdentifier, algorithm, SMIMEAttributeUtils.getDefaultSignedAttributes(),
                    null);
        } catch (IOException e) {
            throw new SMIMEBuilderException(e);
        }
    }

    @Override
    public MimeMessage buildMessage() throws SMIMEBuilderException, MessagingException {
        try {
            MimeMessage newMessage = new MimeMessage(MailSession.getDefaultSession());

            newMessage.setContent(bodyPart.getContent(), bodyPart.getContentType());

            HeaderMatcher contentMatcher = new ContentHeaderNameMatcher();

            /* copy all content headers from body to new message */
            HeaderUtils.copyHeaders(bodyPart, newMessage, contentMatcher);

            /* create a matcher that matches on everything expect content-* */
            HeaderMatcher nonContentMatcher = new NotHeaderNameMatcher(contentMatcher);

            /* copy all non-content headers from source message to the new message */
            HeaderUtils.copyHeaders(sourceMessage, newMessage, nonContentMatcher);

            newMessage.saveChanges();

            return newMessage;
        } catch (IOException e) {
            throw new SMIMEBuilderException(e);
        }
    }

    @Override
    public void compress() throws SMIMEBuilderException {
        try {
            bodyPart = compressedGenerator.generate(bodyPart, SMIMECompressedGenerator.ZLIB);

            /*
             * We will use the deprecated content-type if required. We do this by changing the content-type
             * header. I wish I could specify the content-type for the envelopedGenerator but that's not 
             * possible without completely reimplemening envelopedGenerator. This is a workaround until
             * BC allows me to set the content-type
             */
            if (useDeprecatedContentTypes) {
                bodyPart.setHeader("Content-Type", SMIMEHeader.DEPRECATED_COMPRESSED_CONTENT_TYPE);
            }

            compressedGenerator = new PrivateSMIMECompressedGenerator();
        } catch (SMIMEException e) {
            throw new SMIMEBuilderException(e);
        } catch (MessagingException e) {
            throw new SMIMEBuilderException(e);
        }
    }

    @Override
    public void encrypt(SMIMEEncryptionAlgorithm algorithm, int keySize) throws SMIMEBuilderException {
        try {
            JceCMSContentEncryptorBuilder encryptorBuilder = new JceCMSContentEncryptorBuilder(algorithm.getOID(),
                    keySize);

            encryptorBuilder.setProvider(nonSensitiveProvider);

            bodyPart = envelopedGenerator.generate(bodyPart, encryptorBuilder.build());

            /*
             * We will use the deprecated content-type if required. We do this by changing the content-type
             * header. I wish I could specify the content-type for the envelopedGenerator but that's not 
             * possible without completely reimplemening envelopedGenerator. This is a workaround until
             * BC allows me to set the content-type
             */
            if (useDeprecatedContentTypes) {
                bodyPart.setHeader("Content-Type", SMIMEHeader.DEPRECATED_ENCRYPTED_CONTENT_TYPE);
            }

            envelopedGenerator = new PrivateSMIMEEnvelopedGenerator();
        } catch (SMIMEException e) {
            throw new SMIMEBuilderException(e);
        } catch (MessagingException e) {
            throw new SMIMEBuilderException(e);
        } catch (CMSException e) {
            throw new SMIMEBuilderException(e);
        }
    }

    @Override
    public void encrypt(SMIMEEncryptionAlgorithm algorithm) throws SMIMEBuilderException {
        encrypt(algorithm, algorithm.defaultKeySize());
    }

    @Override
    public void sign(SMIMESignMode signMode) throws SMIMEBuilderException {
        try {
            if (signMode == SMIMESignMode.CLEAR) {
                Multipart signed = signedGenerator.generate(bodyPart);

                /*
                 * We will use the deprecated content-type if required. We do this by changing the content-type
                 * header. I wish I could specify the content-type for the signedGenerator but that's not 
                 * possible without completely reimplemening signedGenerator. This is a workaround until
                 * BC allows me to set the content-type
                 */
                if (useDeprecatedContentTypes) {
                    /*
                     * I don't know how to change the protocol content-type parameter of the Multipart
                     * so we will only change the signature part content type
                     */
                    BodyPart[] parts = SMIMEUtils.dissectSigned(signed);

                    if (parts != null) {
                        BodyPart signaturePart = parts[1];

                        signaturePart.setHeader("Content-Type", SMIMEHeader.DEPRECATED_DETACHED_SIGNATURE_TYPE);
                    }
                }

                MimeMessage signedMessage = new MimeMessage(MailSession.getDefaultSession());

                signedMessage.setContent(signed);
                signedMessage.saveChanges();

                bodyPart = BodyPartUtils.makeContentBodyPart(signedMessage, protectedContentMatcher);
            } else if (signMode == SMIMESignMode.OPAQUE) {
                bodyPart = signedGenerator.generateEncapsulated(bodyPart);

                /*
                 * We will use the deprecated content-type if required. We do this by changing the content-type
                 * header. I wish I could specify the content-type for the signedGenerator but that's not 
                 * possible without completely reimplemening signedGenerator. This is a workaround until
                 * BC allows me to set the content-type
                 */
                if (useDeprecatedContentTypes) {
                    bodyPart.setHeader("Content-Type", SMIMEHeader.DEPRECATED_ENCAPSULATED_SIGNED_CONTENT_TYPE);
                }
            } else {
                throw new IllegalArgumentException("Unknown signMode.");
            }

            this.signedGenerator = new PrivateSMIMESignedGenerator();
        } catch (SMIMEException e) {
            throw new SMIMEBuilderException(e);
        } catch (MessagingException e) {
            throw new SMIMEBuilderException(e);
        } catch (IOException e) {
            throw new SMIMEBuilderException(e);
        }
    }

    /*
     * Create our own private SMIMEEnvelopedGenerator so we can use our own makeContentBodyPart
     */
    private static class PrivateSMIMEEnvelopedGenerator extends SMIMEEnvelopedGenerator {
        @Override
        protected MimeBodyPart makeContentBodyPart(MimeBodyPart content) throws SMIMEException {
            /* we do not need to do any encoding because we already did that */
            return content;
        }
    }

    /*
     * Create our own private SMIMESignedGenerator so we can use our own makeContentBodyPart
     */
    private static class PrivateSMIMESignedGenerator extends SMIMESignedGenerator {
        @Override
        protected MimeBodyPart makeContentBodyPart(MimeBodyPart content) throws SMIMEException {
            /* we do not need to do any encoding because we already did that */
            return content;
        }
    }

    /*
     * Create our own private SMIMECompressedGenerator so we can use our own makeContentBodyPart
     */
    private static class PrivateSMIMECompressedGenerator extends SMIMECompressedGenerator {
        @Override
        protected MimeBodyPart makeContentBodyPart(MimeBodyPart content) throws SMIMEException {
            /* we do not need to do any encoding because we already did that */
            return content;
        }
    }

    @Override
    public void setUseDeprecatedContentTypes(boolean useDeprecatedContentTypes) {
        this.useDeprecatedContentTypes = useDeprecatedContentTypes;
    }

    @Override
    public boolean isUseDeprecatedContentTypes() {
        return useDeprecatedContentTypes;
    }

    public String getNonSensitiveProvider() {
        return nonSensitiveProvider;
    }

    public void setNonSensitiveProvider(String nonSensitiveProvider) {
        this.nonSensitiveProvider = nonSensitiveProvider;
    }

    public String getSensitiveProvider() {
        return sensitiveProvider;
    }

    public void setSensitiveProvider(String sensitiveProvider) {
        this.sensitiveProvider = sensitiveProvider;
    }
}