All Downloads are FREE. Search and download functionalities are using the official Maven repository.

no.difi.oxalis.as2.util.SignedMimeMessage Maven / Gradle / Ivy

/*
 * Copyright 2010-2017 Norwegian Agency for Public Management and eGovernment (Difi)
 *
 * Licensed under the EUPL, Version 1.1 or – as soon they
 * will be approved by the European Commission - subsequent
 * versions of the EUPL (the "Licence");
 *
 * You may not use this work except in compliance with the Licence.
 *
 * You may obtain a copy of the Licence at:
 *
 * https://joinup.ec.europa.eu/community/eupl/og_page/eupl
 *
 * Unless required by applicable law or agreed to in
 * writing, software distributed under the Licence is
 * distributed on an "AS IS" basis,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
 * express or implied.
 * See the Licence for the specific language governing
 * permissions and limitations under the Licence.
 */

package no.difi.oxalis.as2.util;

import com.google.common.io.ByteStreams;
import no.difi.oxalis.api.model.MessageDigestResult;
import no.difi.oxalis.as2.model.Mic;
import no.difi.oxalis.commons.bouncycastle.BCHelper;
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.jce.provider.BouncyCastleProvider;
import org.bouncycastle.mail.smime.SMIMESignedParser;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder;
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.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.DigestInputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Collection;
import java.util.Iterator;

/**
 * Represents an S/MIME message, which provides meta information and data from the signed MimeMessage.
 *
 * @author steinar
 *         Date: 08.10.13
 *         Time: 14:51
 */
public class SignedMimeMessage {

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

    private final MimeMessage mimeMessage;

    private X509Certificate signersX509Certificate;

    static {
        // Installs the Bouncy Castle provider
        BCHelper.registerProvider();
    }

    public SignedMimeMessage(MimeMessage mimeMessage) {
        this.mimeMessage = mimeMessage;
        verifyContentType();

        parseSignedMessage();
    }


    /**
     * Provides an InputStream referencing the payload of the S/MIME message.
     * This includes the entire payload, including the SBDH.
     *
     * @return inputStream pointing to the first byte of the payload.
     */
    public InputStream getPayload() {
        try {
            MimeMultipart mimeMultipart = (MimeMultipart) mimeMessage.getContent();
            BodyPart bodyPart = mimeMultipart.getBodyPart(0);   // First part contains the data, second contains the signature
            return bodyPart.getInputStream();
        } catch (IOException | 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(SMimeDigestMethod algorithm) {
        try {

            MessageDigest messageDigest = BCHelper.getMessageDigest(algorithm.getAlgorithm());

            MimeMultipart mimeMultipart = (MimeMultipart) mimeMessage.getContent();

            BodyPart bodyPart = mimeMultipart.getBodyPart(0);

            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            bodyPart.writeTo(baos); // Writes the entire contents of first multipart, including the MIME headers

            byte[] content = baos.toByteArray();
            messageDigest.update(content);
            String digestAsString = new String(Base64.encode(messageDigest.digest()));

            return new Mic(digestAsString, algorithm);

        } catch (NoSuchAlgorithmException e) {
            throw new IllegalStateException(algorithm.getIdentifier() + " 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);
        }
    }

    /**
     * Calculates the message digest of the payload
     *
     * @return the Message digest for the payload
     */
    public MessageDigestResult calcPayloadDigest(String algorithmName) {

        MessageDigest instance;
        try {
            instance = MessageDigest.getInstance(algorithmName);
        } catch (NoSuchAlgorithmException e) {
            throw new IllegalStateException("Unable to create message digester " + e.getMessage(), e);
        }
        DigestInputStream digestInputStream = new DigestInputStream(getPayload(), instance);
        try {
            ByteStreams.exhaust(digestInputStream);
        } catch (IOException e) {
            throw new IllegalStateException("Error while reading Mime message payload for calculating digest." + e.getMessage(), e);
        }

        return new MessageDigestResult(instance.digest(), instance.getAlgorithm());
    }


    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();

            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;
        try {
            // MimeMessageHelper.dumpMimePartToFile("/tmp/parseSignedMessage.txt", mimeMessage);
            smimeSignedParser = new SMIMESignedParser(new JcaDigestCalculatorProviderBuilder().build(), (MimeMultipart) mimeMessage.getContent());
        } catch (MessagingException | CMSException | IOException | OperatorCreationException e) {
            throw new IllegalStateException("Unable to create SMIMESignedParser: " + e.getMessage(), e);
        }

        Store certs;
        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;
        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
            @SuppressWarnings("unchecked")
            Collection certCollection = certs.getMatches(signer.getSID());

            // Retrieve the first certificate
            Iterator certIt = certCollection.iterator();
            if (certIt.hasNext()) {
                try {
                    signersX509Certificate = new JcaX509CertificateConverter().setProvider(BouncyCastleProvider.PROVIDER_NAME).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(BouncyCastleProvider.PROVIDER_NAME).build(signersX509Certificate))) {
                    throw new IllegalStateException("Verification of signer failed");
                }
            } catch (CMSException | OperatorCreationException e) {
                throw new IllegalStateException("Unable to verify the signer. " + e.getMessage(), e);
            }
            */

            String issuerDN = signersX509Certificate.getIssuerDN().toString();
            log.debug("Certificate issued by: " + issuerDN);

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

    }
}





© 2015 - 2024 Weber Informatics LLC | Privacy Policy