mitm.common.tools.SMIME.java Source code

Java tutorial

Introduction

Here is the source code for mitm.common.tools.SMIME.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.tools;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.security.AlgorithmParameters;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.PrivateKey;
import java.security.cert.CertSelector;
import java.security.cert.CertificateParsingException;
import java.security.cert.X509CRL;
import java.security.cert.X509Certificate;
import java.util.Enumeration;
import java.util.List;

import javax.mail.MessagingException;
import javax.mail.internet.MimeMessage;

import jline.ConsoleReader;
import mitm.common.mail.MailUtils;
import mitm.common.security.SecurityFactory;
import mitm.common.security.SecurityFactoryFactory;
import mitm.common.security.asn1.ASN1Utils;
import mitm.common.security.bouncycastle.InitializeBouncycastle;
import mitm.common.security.certificate.CertificateUtils;
import mitm.common.security.certificate.X509CertificateInspector;
import mitm.common.security.cms.CryptoMessageSyntaxException;
import mitm.common.security.cms.KeyNotFoundException;
import mitm.common.security.cms.RecipientInfo;
import mitm.common.security.cms.SignerInfo;
import mitm.common.security.cms.SignerInfoException;
import mitm.common.security.keystore.BasicKeyStore;
import mitm.common.security.keystore.KeyStoreKeyProvider;
import mitm.common.security.smime.SMIMEBuilder;
import mitm.common.security.smime.SMIMEBuilderImpl;
import mitm.common.security.smime.SMIMECapabilitiesInspector;
import mitm.common.security.smime.SMIMECapabilityInfo;
import mitm.common.security.smime.SMIMECompressedInspector;
import mitm.common.security.smime.SMIMEEncryptionAlgorithm;
import mitm.common.security.smime.SMIMEEnvelopedInspector;
import mitm.common.security.smime.SMIMEInspector;
import mitm.common.security.smime.SMIMEInspectorImpl;
import mitm.common.security.smime.SMIMESignMode;
import mitm.common.security.smime.SMIMESignedInspector;
import mitm.common.security.smime.SMIMESigningAlgorithm;
import mitm.common.security.smime.SMIMEType;
import mitm.common.util.MiscStringUtils;

import org.apache.commons.cli.BasicParser;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.MissingArgumentException;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.OptionBuilder;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.PropertyConfigurator;
import org.bouncycastle.asn1.cms.AttributeTable;
import org.bouncycastle.mail.smime.SMIMEException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class SMIME {
    private static final Logger logger = LoggerFactory.getLogger(SMIME.class);

    private static SecurityFactory securityFactory;

    @SuppressWarnings("static-access")
    private static Options createCommandLineOptions() {
        Options options = new Options();

        Option inOption = OptionBuilder.withArgName("in").hasArg().withDescription("input file").create("in");
        inOption.setRequired(false);
        options.addOption(inOption);

        Option keyStoreOption = OptionBuilder.withArgName("p12").hasArg().withDescription("keystore (.p12/.pfx)")
                .create("p12");
        keyStoreOption.setRequired(false);
        options.addOption(keyStoreOption);

        Option passwordOption = OptionBuilder.withArgName("password").hasArg().withDescription("password")
                .create("password");
        passwordOption.setRequired(false);
        options.addOption(passwordOption);

        Option passwordPromptOption = OptionBuilder.withDescription("ask for password").create("pwd");
        passwordPromptOption.setRequired(false);
        options.addOption(passwordPromptOption);

        Option helpOption = OptionBuilder.withDescription("Show help").create("help");
        helpOption.setRequired(false);
        options.addOption(helpOption);

        Option p7mOption = OptionBuilder.withDescription("file is a p7m file").create("p7m");
        p7mOption.setRequired(false);
        options.addOption(p7mOption);

        Option p7mOutputOption = OptionBuilder.withArgName("p7m output file").hasArg()
                .withDescription("save the p7m as S/MIME to p7mOut").create("p7mOut");
        p7mOutputOption.setRequired(false);
        options.addOption(p7mOutputOption);

        Option binaryOption = OptionBuilder.withDescription("file is binary").create("binary");
        binaryOption.setRequired(false);
        options.addOption(binaryOption);

        Option cerOutputOption = OptionBuilder.withArgName("cer output file").hasArg()
                .withDescription("saves the certificates from the signed blob to this file in p7b format.")
                .create("cerOut");
        cerOutputOption.setRequired(false);
        options.addOption(cerOutputOption);

        Option readOption = OptionBuilder.withDescription("read S/MIME file").create("r");
        readOption.setRequired(false);
        options.addOption(readOption);

        Option signOption = OptionBuilder.withDescription("sign file").create("sign");
        signOption.setRequired(false);
        options.addOption(signOption);

        Option keyAliasOption = OptionBuilder.withArgName("alias").hasArg().withDescription("key alias")
                .create("alias");
        keyAliasOption.setRequired(false);
        options.addOption(keyAliasOption);

        Option printAliasesOption = OptionBuilder.withDescription("print key aliases").create("printAliases");
        printAliasesOption.setRequired(false);
        options.addOption(printAliasesOption);

        Option digestOption = OptionBuilder.withDescription("hash algorithm").hasArg()
                .withDescription("digest hash algorithm").create("digest");
        digestOption.setRequired(false);
        options.addOption(digestOption);

        Option outOption = OptionBuilder.withDescription("output file").hasArg().withDescription("output file")
                .create("out");
        outOption.setRequired(false);
        options.addOption(outOption);

        return options;
    }

    private static MimeMessage loadMessage(String filename) throws FileNotFoundException, MessagingException {
        File mail = new File(filename);

        mail = mail.getAbsoluteFile();

        MimeMessage message = MailUtils.loadMessage(mail);

        return message;
    }

    private static MimeMessage loadp7m(String filename, boolean binary) throws Exception {
        File p7mFile = new File(filename);

        p7mFile = p7mFile.getAbsoluteFile();

        FileInputStream fis = new FileInputStream(p7mFile);

        ByteArrayOutputStream bos = new ByteArrayOutputStream();

        OutputStreamWriter sw = new OutputStreamWriter(bos);

        sw.append("Content-Type: application/pkcs7-mime; name=\"smime.p7m\"\r\n");
        sw.append("Content-Transfer-Encoding: base64\r\n");
        sw.append("\r\n\r\n");

        byte[] content = IOUtils.toByteArray(fis);

        if (binary) {
            content = Base64.encodeBase64Chunked(content);
        }

        String base64Content = MiscStringUtils.toAsciiString(content);

        sw.append(base64Content);

        sw.flush();

        MimeMessage message = MailUtils.byteArrayToMessage(bos.toByteArray());

        return message;
    }

    private static KeyStore loadKeyStore(String keyFile, String password) throws Exception {
        File file = new File(keyFile);

        file = file.getAbsoluteFile();

        KeyStore keyStore = securityFactory.createKeyStore("PKCS12");

        /* initialize key store */
        keyStore.load(new FileInputStream(file), password != null ? password.toCharArray() : null);

        return keyStore;
    }

    private static void inspectEnveloped(SMIMEEnvelopedInspector inspector) throws Exception {
        System.err.println("==============================");
        System.err.println("Encrypted message");
        System.err.println();

        String algorithmName = inspector.getEncryptionAlgorithmOID();

        SMIMEEncryptionAlgorithm encryptionAlg = SMIMEEncryptionAlgorithm.fromOID(algorithmName);

        if (encryptionAlg != null) {
            algorithmName = encryptionAlg.toString();
        }

        System.err.print("Encryption Algorithm: " + algorithmName);

        if (encryptionAlg != null) {
            AlgorithmParameters parameters = inspector.getEncryptionAlgorithmParameters();
            System.err.print(", Key size: " + SMIMEEncryptionAlgorithm.getKeySize(encryptionAlg, parameters));
        }

        System.err.println();
        System.err.println();

        List<RecipientInfo> recipients = inspector.getRecipients();

        for (int i = 0; i < recipients.size(); i++) {
            RecipientInfo recipient = recipients.get(i);

            System.err.println("*** Recipient " + i);
            System.err.println();
            System.err.println(recipient.toString());
            System.err.println();
        }

        System.err.println();
        System.err.println("Unprotected attributes:");
        System.err.println(ASN1Utils.dump(inspector.getUnprotectedAttributes()));

        System.err.println("*** Decrypted message:");
        System.err.println();

        try {
            inspector.getContentAsMimeMessage().writeTo(System.out);
        } catch (KeyNotFoundException e) {
            System.err.println("Decryption key could not be found.");
        }
    }

    private static void dumpSMIMECapabilities(AttributeTable attributes) {
        List<SMIMECapabilityInfo> capabilities = SMIMECapabilitiesInspector.inspect(attributes);

        System.err.println("SMIME capabilities");
        System.err.println();

        for (SMIMECapabilityInfo capabilityInfo : capabilities) {
            System.err.println(capabilityInfo);
        }

        System.err.println();
    }

    private static void inspectSigned(SMIMESignedInspector inspector, String cerOut) throws Exception {
        System.err.println("==============================");
        System.err.println("Signed message");
        System.err.println();

        System.err.println("CMSVersion: " + inspector.getVersion());
        System.err.println();

        List<SignerInfo> signers = inspector.getSigners();

        List<X509Certificate> certificates = inspector.getCertificates();

        for (int i = 0; i < signers.size(); i++) {
            SignerInfo signer = signers.get(i);

            System.err.println("*** [Signer " + i + "] ***");
            System.err.println();
            System.err.println(signer);
            System.err.println();

            dumpSMIMECapabilities(signer.getSignedAttributes());

            CertSelector selector = signer.getSignerId().getSelector();

            List<X509Certificate> signingCerts = CertificateUtils.getMatchingCertificates(certificates, selector);

            if (signingCerts.size() > 0) {
                /* there could be more certificates but get the first one */
                X509Certificate certificate = signingCerts.get(0);

                try {
                    if (signer.verify(certificate.getPublicKey())) {
                        System.err.println("Verification OK.");
                    }
                } catch (SignerInfoException e) {
                    System.err.println("* WARNING: verification failed. Message: " + e.getMessage());
                }
            } else {
                System.err.println("* WARNING: Signing certificate not found so unable to verify signature *");
            }
        }

        System.err.println("==============================");
        System.err.println("Certificates:");
        System.err.println();

        for (int i = 0; i < certificates.size(); i++) {
            X509Certificate certificate = certificates.get(i);

            System.err.println("*** Certificate " + i);
            System.err.println();
            System.err.println(certificate);
            System.err.println();

            try {
                System.err.println("Extra information:");
                System.err.println();

                X509CertificateInspector certInspector = new X509CertificateInspector(certificate);

                System.err.println("SubjectKeyIdentifier: " + certInspector.getSubjectKeyIdentifierHex());
                System.err.println("Email: " + certInspector.getEmail());
                System.err.println();
            } catch (CertificateParsingException e) {
                logger.error("Error while parsing the certificate", e);
            }
        }

        if (cerOut != null) {
            File outfile = new File(cerOut);

            CertificateUtils.writeCertificates(certificates, new FileOutputStream(outfile));
        }

        List<X509CRL> crls = inspector.getCRLs();

        System.err.println("==============================");
        System.err.println("CRLs:");
        System.err.println();

        for (int i = 0; i < crls.size(); i++) {
            X509CRL crl = crls.get(i);

            System.err.println("*** CRL " + i);
            System.err.println();
            System.err.println(crl);
            System.err.println();
        }

        System.err.println("*** Unsigned message:");
        System.err.println();

        inspector.getContentAsMimeMessage().writeTo(System.out);
    }

    private static void inspectCompressed(SMIMECompressedInspector inspector) throws Exception {
        System.err.println("==============================");
        System.err.println("Compressed message");
        System.err.println();
        System.err.println("Decompressed message:");
        System.err.println();

        inspector.getContentAsMimeMessage().writeTo(System.out);
    }

    private static void inspectMessage(MimeMessage message, BasicKeyStore basicKeyStore, String cerOut)
            throws Exception {
        SMIMEInspector inspector = new SMIMEInspectorImpl(message, basicKeyStore,
                securityFactory.getNonSensitiveProvider(), securityFactory.getSensitiveProvider());

        if (inspector.getSMIMEType() == SMIMEType.NONE) {
            System.err.println("Message is not a S/MIME message.");

            return;
        }

        switch (inspector.getSMIMEType()) {
        case ENCRYPTED:
            inspectEnveloped(inspector.getEnvelopedInspector());
            break;
        case SIGNED:
            inspectSigned(inspector.getSignedInspector(), cerOut);
            break;
        case COMPRESSED:
            inspectCompressed(inspector.getCompressedInspector());
            break;
        default:
            break;
        }

        return;
    }

    private static void printKeystoreAliases(KeyStore keyStore) throws KeyStoreException {
        Enumeration<String> aliases = keyStore.aliases();

        System.out.println("**** BEGIN KEY ALIASES ***");

        while (aliases.hasMoreElements()) {
            System.out.println(aliases.nextElement());
        }

        System.out.println("**** END KEY ALIASES ***");
    }

    private static void sign(MimeMessage source, KeyStore keyStore, String alias, String password,
            String digestAlgo, String outFile) throws Exception {
        if (StringUtils.isEmpty(alias)) {
            throw new MissingArgumentException("alias is missing.");
        }

        KeyStore.Entry entry = keyStore.getEntry(alias, new KeyStore.PasswordProtection(password.toCharArray()));

        if (!(entry instanceof KeyStore.PrivateKeyEntry)) {
            throw new KeyStoreException("Key is not a PrivateKeyEntry.");
        }

        KeyStore.PrivateKeyEntry privateKeyEntry = (KeyStore.PrivateKeyEntry) entry;

        X509Certificate certificate = (X509Certificate) privateKeyEntry.getCertificate();
        PrivateKey key = privateKeyEntry.getPrivateKey();

        if (certificate == null) {
            throw new KeyStoreException("Entry does not have a certificate.");
        }

        if (key == null) {
            throw new KeyStoreException("Entry does not have a private key.");
        }

        SMIMESigningAlgorithm signingAlgorithm;

        if (StringUtils.isNotEmpty(digestAlgo)) {
            signingAlgorithm = SMIMESigningAlgorithm.fromName(digestAlgo);

            if (signingAlgorithm == null) {
                throw new IllegalArgumentException(digestAlgo + " is not a valid digest.");
            }
        } else {
            signingAlgorithm = SMIMESigningAlgorithm.SHA1WITHRSA;
        }

        SMIMEBuilder builder = new SMIMEBuilderImpl(source);

        builder.addCertificates(certificate);
        builder.addSigner(key, certificate, signingAlgorithm);

        builder.sign(SMIMESignMode.CLEAR);

        MimeMessage signed = builder.buildMessage();

        if (signed == null) {
            throw new SMIMEException("Message could not be signed");
        }

        MailUtils.writeMessage(signed, new File(outFile));
    }

    /**
     * @param args
     * @throws CryptoMessageSyntaxException
     */
    public static void main(String[] args) {
        try {
            PropertyConfigurator.configure("conf/tools.log4j.properties");

            InitializeBouncycastle.initialize();

            securityFactory = SecurityFactoryFactory.getSecurityFactory();

            CommandLineParser parser = new BasicParser();

            Options options = createCommandLineOptions();

            HelpFormatter formatter = new HelpFormatter();

            CommandLine commandLine;

            try {
                commandLine = parser.parse(options, args);
            } catch (ParseException e) {
                formatter.printHelp("SMIME", options, true);

                throw e;
            }

            String inFile = commandLine.getOptionValue("in");
            String keyFile = commandLine.getOptionValue("p12");
            String password = commandLine.getOptionValue("password");
            boolean binary = commandLine.hasOption("binary");
            String p7mOut = commandLine.getOptionValue("p7mOut");
            String cerOut = commandLine.getOptionValue("cerOut");
            String alias = commandLine.getOptionValue("alias");
            String digest = commandLine.getOptionValue("digest");
            String outFile = commandLine.getOptionValue("out");

            if (commandLine.hasOption("help") || args == null || args.length == 0) {
                formatter.printHelp("SMIME", options, true);

                return;
            }

            if (commandLine.hasOption("pwd")) {
                System.err.println("Please enter your password: ");

                /*
                 * We will redirect the output to err so we do not get any * chars on the output.
                 */
                ConsoleReader consoleReader = new ConsoleReader(new FileInputStream(FileDescriptor.in),
                        new PrintWriter(System.err));

                password = consoleReader.readLine(new Character('*'));
            }

            KeyStore keyStore = null;

            if (keyFile != null) {
                keyStore = loadKeyStore(keyFile, password);
            }

            if (commandLine.hasOption("printAliases")) {
                if (keyStore == null) {
                    throw new MissingArgumentException("p12 file is missing.");
                }

                printKeystoreAliases(keyStore);
            }

            MimeMessage message;

            if (commandLine.hasOption("r")) {
                if (commandLine.hasOption("p7m")) {
                    message = loadp7m(inFile, binary);

                    if (commandLine.hasOption("p7mOut")) {
                        MailUtils.writeMessage(message, new File(p7mOut));
                    }
                } else {
                    message = loadMessage(inFile);
                }

                KeyStoreKeyProvider basicKeyStore = null;

                if (keyStore != null) {
                    basicKeyStore = new KeyStoreKeyProvider(keyStore, "test");

                    basicKeyStore.setUseOL2010Workaround(true);
                }

                if (message == null) {
                    throw new MissingArgumentException("in file is not specified");
                }

                inspectMessage(message, basicKeyStore, cerOut);
            } else if (commandLine.hasOption("sign")) {
                message = loadMessage(inFile);

                if (message == null) {
                    throw new MissingArgumentException("in file is not specified");
                }

                if (StringUtils.isEmpty(outFile)) {
                    throw new MissingArgumentException("out file is not specified");
                }

                sign(message, keyStore, alias, password, digest, outFile);
            }
        } catch (MissingArgumentException e) {
            System.err.println("Not all required parameters are specified. " + e);
        } catch (ParseException e) {
            System.err.println("Command line parsing error. " + e);
        } catch (Exception e) {
            logger.error("Some error ocurred", e);
        }
    }
}