eu.peppol.as2.SignedMimeMessageInspector.java Source code

Java tutorial

Introduction

Here is the source code for eu.peppol.as2.SignedMimeMessageInspector.java

Source

/*
 * Copyright (c) 2011,2012,2013 UNIT4 Agresso AS.
 *
 * This file is part of Oxalis.
 *
 * Oxalis is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Oxalis 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with Oxalis.  If not, see <http://www.gnu.org/licenses/>.
 */

package eu.peppol.as2;

import com.sun.xml.ws.transport.tcp.io.ByteBufferOutputStream;
import eu.peppol.security.KeystoreManager;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.cms.CMSException;
import org.bouncycastle.cms.SignerInformation;
import org.bouncycastle.cms.SignerInformationStore;
import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoVerifierBuilder;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.mail.smime.SMIMESignedParser;
import org.bouncycastle.operator.DigestCalculatorProvider;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.operator.bc.BcDigestCalculatorProvider;
import org.bouncycastle.util.Store;
import org.bouncycastle.util.encoders.Base64;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.mail.BodyPart;
import javax.mail.MessagingException;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.*;
import java.security.cert.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;

/**
 * Inspects and provides information about a MimeMessage
 *
 * @author steinar
 *         Date: 08.10.13
 *         Time: 14:51
 */
public class SignedMimeMessageInspector {

    private static final Logger log = LoggerFactory.getLogger(SignedMimeMessageInspector.class);

    private final MimeMessage mimeMessage;
    private X509Certificate signersX509Certificate;
    private BouncyCastleProvider provider;

    public SignedMimeMessageInspector(MimeMessage mimeMessage) {
        provider = new BouncyCastleProvider();
        Security.addProvider(provider);
        this.mimeMessage = mimeMessage;
        verifyContentType();
        parseSignedMessage();
    }

    private void verifyContentType() {
        try {

            // at this stage we should have a javax.mail.internet.MimeMessage with content type text/plain
            log.debug("Verifying " + mimeMessage.getClass().getName() + " with content type "
                    + mimeMessage.getContentType());

            // the contents of this should be a multipart MimeMultipart that is signed
            String contentType = ((MimeMultipart) mimeMessage.getContent()).getContentType();

            /*
            // Debug mimeMessage javax.mail.internet.MimeMessage with content type text/plain
            String contentType = mimeMessage.getContentType();
            if ("text/plain".equalsIgnoreCase(contentType)) {
            String content = (String) mimeMessage.getContent();
            log.debug("Verifying mimeMessage --" + contentType + "-start--" + content + "--" + contentType + "-end--");
            }
            */

            if (!contentType.startsWith("multipart/signed")) {
                throw new IllegalStateException("MimeMessage is not multipart/signed, it is : " + contentType);
            }

        } catch (Exception e) {
            throw new IllegalStateException("Unable to retrieve content type from MimeMessage. " + e.getMessage(),
                    e);
        }
    }

    void parseSignedMessage() {
        SMIMESignedParser smimeSignedParser = null;
        try {
            // MimeMessageHelper.dumpMimePartToFile("/tmp/parseSignedMessage.txt", mimeMessage);
            smimeSignedParser = new SMIMESignedParser(new BcDigestCalculatorProvider(),
                    (MimeMultipart) mimeMessage.getContent());
        } catch (MessagingException e) {
            throw new IllegalStateException("Unable to get content of message." + e.getMessage(), e);
        } catch (CMSException e) {
            throw new IllegalStateException("Unable to get content of message. " + e.getMessage(), e);
        } catch (IOException e) {
            throw new IllegalStateException("Unable to get content of message. " + e.getMessage(), e);
        }

        Store certs = null;
        try {
            certs = smimeSignedParser.getCertificates();
        } catch (CMSException e) {
            throw new IllegalStateException("Unable to retrieve the certificates from signed message.");
        }

        //
        // SignerInfo blocks which contain the signatures
        //
        SignerInformationStore signerInfos = null;
        try {
            signerInfos = smimeSignedParser.getSignerInfos();
        } catch (CMSException e) {
            throw new IllegalStateException("Unable to get the Signer information from message. " + e.getMessage(),
                    e);
        }

        Collection signers = signerInfos.getSigners();
        Iterator signersIterator = signers.iterator();

        //
        // Only a single signer, get the first and only certificate
        //
        if (signersIterator.hasNext()) {

            // Retrieves information on first and only signer
            SignerInformation signer = (SignerInformation) signersIterator.next();

            // Retrieves the collection of certificates for first and only signer
            Collection certCollection = certs.getMatches(signer.getSID());

            // Retrieve the first certificate
            Iterator certIt = certCollection.iterator();
            if (certIt.hasNext()) {
                try {
                    signersX509Certificate = new JcaX509CertificateConverter().setProvider(provider)
                            .getCertificate((X509CertificateHolder) certIt.next());
                } catch (CertificateException e) {
                    throw new IllegalStateException("Unable to fetch certificate for signer. " + e.getMessage(), e);
                }
            } else {
                throw new IllegalStateException(
                        "Signers certificate was not found, unable to verify the signature");
            }

            // Verify that the signature is correct and that signersIterator was generated when the certificate was current
            try {
                if (!signer.verify(new JcaSimpleSignerInfoVerifierBuilder().setProvider(provider)
                        .build(signersX509Certificate))) {
                    throw new IllegalStateException("Verification of signer failed");
                }
            } catch (CMSException e) {
                throw new IllegalStateException("Unable to verify the signer. " + e.getMessage(), e);
            } catch (OperatorCreationException e) {
                throw new IllegalStateException("Unable to verify the signer. " + e.getMessage(), e);
            }

            // Verify that the certificate issuer is trusted
            String issuerDN = signersX509Certificate.getIssuerDN().toString();
            log.debug("Verify the certificate issuer : " + issuerDN);
            //TODO validateCertificate(signersX509Certificate);

        } else {
            throw new IllegalStateException("There is no signer information available");
        }

    }

    private void validateCertificate(X509Certificate certificate) {

        try {

            List<X509Certificate> certificateList = new ArrayList<X509Certificate>();
            certificateList.add(certificate);

            CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
            CertPath certPath = certificateFactory.generateCertPath(certificateList);

            // Create the parameters for the validator
            KeystoreManager keystoreManager = KeystoreManager.getInstance();
            PKIXParameters params = new PKIXParameters(keystoreManager.getPeppolTruststore());

            // Disable revocation checking as we trust our own truststore (and do not have a CRL and don't want OCSP)
            params.setRevocationEnabled(false);

            // Validate the certificate path
            CertPathValidator pathValidator = CertPathValidator.getInstance("PKIX", provider);
            CertPathValidatorResult validatorResult = pathValidator.validate(certPath, params);

            // Get the CA used to validate this path
            PKIXCertPathValidatorResult result = (PKIXCertPathValidatorResult) validatorResult;
            TrustAnchor ta = result.getTrustAnchor();
            X509Certificate trustCert = ta.getTrustedCert();

            log.debug("Trusted cert was : {}", trustCert.getSubjectDN().toString());

        } catch (Exception e) {
            throw new IllegalStateException("Unable to trust the signer : " + e.getMessage(), e);
        }

    }

    public InputStream getPayload() {
        try {
            MimeMultipart mimeMultipart = (MimeMultipart) mimeMessage.getContent();
            BodyPart bodyPart = mimeMultipart.getBodyPart(0); // First part contains the data, second contains the signature
            InputStream inputStream = bodyPart.getInputStream();
            return inputStream;
        } catch (IOException e) {
            throw new IllegalStateException(
                    "Unable to access the contents of the payload in first body part. " + e.getMessage(), e);
        } catch (MessagingException e) {
            throw new IllegalStateException(
                    "Unable to access the contents of the payload in first body part. " + e.getMessage(), e);
        }
    }

    public MimeMessage getMimeMessage() {
        return mimeMessage;
    }

    public X509Certificate getSignersX509Certificate() {
        return signersX509Certificate;
    }

    public Mic calculateMic(String algorithmName) {
        try {

            MessageDigest messageDigest = MessageDigest.getInstance(algorithmName, provider);

            MimeMultipart mimeMultipart = (MimeMultipart) mimeMessage.getContent();
            BodyPart bodyPart = mimeMultipart.getBodyPart(0);
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            bodyPart.writeTo(baos);
            // bodyPart.writeTo(System.out);
            byte[] content = baos.toByteArray();
            messageDigest.update(content);
            String digestAsString = new String(Base64.encode(messageDigest.digest()));
            return new Mic(digestAsString, algorithmName);

            /*
            InputStream resourceAsStream = getPayload() / getInputStreamForMimeMessage();
            DigestInputStream digestInputStream = new DigestInputStream(resourceAsStream, messageDigest);
                
            // Reads through the entire file in order to create the digest
            final byte[] aBuf = new byte[4096];
            while (digestInputStream.read(aBuf) >= 0) {
            digestInputStream.close();
            }
                
            // grabs the digest after reading all of the contents.
            byte[] digest = digestInputStream.getMessageDigest().digest();
            String digestAsString = new String(Base64.encode(digest));
                
            return new Mic(digestAsString, algorithmName);
            */

        } catch (NoSuchAlgorithmException e) {
            throw new IllegalStateException(algorithmName + " not found", e);
        } catch (IOException e) {
            throw new IllegalStateException("Unable to read data from digest input. " + e.getMessage(), e);
        } catch (MessagingException e) {
            throw new IllegalStateException("Unable to handle mime body part. " + e.getMessage(), e);
        }
    }

    private InputStream getInputStreamForMimeMessage() {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        try {
            getMimeMessage().writeTo(baos);
        } catch (IOException e) {
            throw new IllegalStateException(
                    "Unable to write MIME message to byte array output stream: " + e.getMessage(), e);
        } catch (MessagingException e) {
            throw new IllegalStateException("Unable to read contents of MIME message; " + e.getMessage(), e);
        }
        return new ByteArrayInputStream(baos.toByteArray());
    }

}