de.mendelson.comm.as2.message.AS2MessageParser.java Source code

Java tutorial

Introduction

Here is the source code for de.mendelson.comm.as2.message.AS2MessageParser.java

Source

//$Header: /cvsroot-fuse/mec-as2/39/mendelson/comm/as2/message/AS2MessageParser.java,v 1.1 2012/04/18 14:10:30 heller Exp $
package de.mendelson.comm.as2.message;

import de.mendelson.comm.as2.AS2Exception;
import de.mendelson.comm.as2.notification.Notification;
import de.mendelson.util.security.cert.CertificateManager;
import de.mendelson.comm.as2.partner.Partner;
import de.mendelson.comm.as2.partner.PartnerAccessDB;
import de.mendelson.util.AS2Tools;
import de.mendelson.util.MecResourceBundle;
import de.mendelson.util.security.BCCryptoHelper;
import javax.mail.util.ByteArrayDataSource;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.sql.Connection;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.MissingResourceException;
import java.util.Properties;
import java.util.ResourceBundle;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.activation.DataHandler;
import javax.mail.BodyPart;
import javax.mail.MessagingException;
import javax.mail.Multipart;
import javax.mail.Part;
import javax.mail.Session;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;
import javax.mail.internet.MimeUtility;
import org.apache.commons.io.output.DeferredFileOutputStream;
import org.bouncycastle.cms.RecipientId;
import org.bouncycastle.cms.RecipientInformation;
import org.bouncycastle.cms.RecipientInformationStore;
import org.bouncycastle.cms.jcajce.JceKeyTransEnvelopedRecipient;
import org.bouncycastle.cms.jcajce.JceKeyTransRecipientId;
import org.bouncycastle.mail.smime.SMIMECompressed;
import org.bouncycastle.mail.smime.SMIMEEnveloped;

/*
 * Copyright (C) mendelson-e-commerce GmbH Berlin Germany
 *
 * This software is subject to the license agreement set forth in the license.
 * Please read and agree to all terms before using this software.
 * Other product and brand names are trademarks of their respective owners.
 */
/**
 * Analyzes and builds AS2 messages
 * @author S.Heller
 * @version $Revision: 1.1 $
 */
public class AS2MessageParser {

    private Logger logger = null;
    /**Access to all certificates*/
    private CertificateManager certificateManagerSignature;
    private CertificateManager certificateManagerEncryption;
    private MecResourceBundle rb = null;
    private MecResourceBundle rbMessage = null;
    //DB connection
    private Connection configConnection = null;
    private Connection runtimeConnection = null;

    public AS2MessageParser() {
        //Load resourcebundle
        try {
            this.rb = (MecResourceBundle) ResourceBundle.getBundle(ResourceBundleAS2MessageParser.class.getName());
            this.rbMessage = (MecResourceBundle) ResourceBundle.getBundle(ResourceBundleAS2Message.class.getName());
        } //load up  resourcebundle
        catch (MissingResourceException e) {
            throw new RuntimeException("Oops..resource bundle " + e.getClassName() + " not found.");
        }
    }

    /**Passes the certificate manager to this class*/
    public void setCertificateManager(CertificateManager certificateManagerSignature,
            CertificateManager certificateManagerEncryption) {
        this.certificateManagerEncryption = certificateManagerEncryption;
        this.certificateManagerSignature = certificateManagerSignature;
    }

    /**Passes a db connection to this class*/
    public void setDBConnection(Connection configConnection, Connection runtimeConnection) {
        this.configConnection = configConnection;
        this.runtimeConnection = runtimeConnection;
    }

    /**Passes a logger to this class*/
    public void setLogger(Logger logger) {
        this.logger = logger;
    }

    /**Unescapes the AS2-TO and AS2-FROM headers in receiver direction, related to
     * RFC 4130 section 6.2
     * @param identification as2-from or as2-to value to unescape
     * @return unescaped value
     */
    public static String unescapeFromToHeader(String identification) {
        StringBuilder builder = new StringBuilder();
        //remove quotes
        if (identification.startsWith("\"") && identification.endsWith("\"")) {
            identification = identification.substring(1, identification.length() - 1);
        }
        boolean inEscape = false;
        for (int i = 0; i < identification.length(); i++) {
            char singleChar = identification.charAt(i);
            if (singleChar == '\\') {
                if (inEscape) {
                    builder.append(singleChar);
                    inEscape = false;
                } else {
                    inEscape = true;
                }
            } else {
                builder.append(singleChar);
                inEscape = false;
            }

        }
        return (builder.toString());
    }

    /**Displays a bundle of byte arrays as hex string, for debug purpose only*/
    private String toHexDisplay(byte[] data) {
        StringBuilder result = new StringBuilder();
        for (int i = 0; i < data.length; i++) {
            result.append(Integer.toString((data[i] & 0xff) + 0x100, 16).substring(1));
            result.append(" ");
        }
        return result.toString();
    }

    /**Uncompresses message data*/
    public byte[] decompressData(AS2MessageInfo info, byte[] data, String contentType) throws Exception {
        MimeBodyPart compressedPart = new MimeBodyPart();
        compressedPart.setDataHandler(new DataHandler(new ByteArrayDataSource(data, contentType)));
        compressedPart.setHeader("content-type", contentType);
        return (this.decompressData(info, new SMIMECompressed(compressedPart), compressedPart.getSize()));
    }

    /**Uncompresses message data*/
    public byte[] decompressData(AS2MessageInfo info, SMIMECompressed compressed, long compressedSize)
            throws Exception {
        byte[] decompressedData = compressed.getContent();
        info.setCompressionType(AS2Message.COMPRESSION_ZLIB);
        if (this.logger != null) {
            this.logger.log(Level.INFO,
                    this.rb.getResourceString("data.compressed.expanded",
                            new Object[] { info.getMessageId(), AS2Tools.getDataSizeDisplay(compressedSize),
                                    AS2Tools.getDataSizeDisplay(decompressedData.length) }),
                    info);
        }
        return (decompressedData);
    }

    /**Decodes data by its content transfer encoding and returns it*/
    private byte[] decodeContentTransferEncoding(byte[] encodedData, String contentTransferEncoding)
            throws Exception {
        ByteArrayInputStream bais = new ByteArrayInputStream(encodedData);
        InputStream b64is = MimeUtility.decode(bais, contentTransferEncoding);
        byte[] tmp = new byte[encodedData.length];
        int n = b64is.read(tmp);
        byte[] res = new byte[n];
        System.arraycopy(tmp, 0, res, 0, n);
        return res;
    }

    /**If a content transfer encoding is set this will decode the data*/
    private byte[] processContentTransferEncoding(byte[] data, Properties header) throws Exception {
        if (!header.containsKey("content-transfer-encoding")) {
            //no content transfer encoding set: the AS2 default is "binary" in this case (NOT 7bit!), binary
            //content transfer encoding requires no decoding
            return (data);
        } else {
            String transferEncoding = header.getProperty("content-transfer-encoding");
            return (this.decodeContentTransferEncoding(data, transferEncoding));
        }
    }

    private AS2Message createFromMDNRequest(byte[] rawMessageData, Properties header, String contentType,
            AS2MDNInfo mdnInfo, MDNParser mdnParser) throws Exception {
        AS2MessageInfo relatedMessageInfo = new AS2MessageInfo();
        relatedMessageInfo.setMessageId(mdnInfo.getRelatedMessageId());
        //generate a new MDN id for this MDN if none is provided. message ids are not required for MDN in the RFC:
        //section 7.6 (Receipt Reply Considerations in HTTP POST) -->  "an MDN SHOULD have its own unique Message-ID header."
        if (mdnInfo.getMessageId() == null) {
            mdnInfo.setMessageId("<MDN_ON_" + mdnInfo.getRelatedMessageId() + ">");
        }
        MDNAccessDB mdnAccess = new MDNAccessDB(this.configConnection, this.runtimeConnection);
        MessageAccessDB messageAccess = new MessageAccessDB(this.configConnection, this.runtimeConnection);
        //check if related message id exists in db
        if (!messageAccess.messageIdExists(mdnInfo.getRelatedMessageId())) {
            //there is no way to log this persistent because the MDN is not related to any message: Just write the
            //incoming event to the log without binding it to a as message
            if (this.logger != null) {
                this.logger.log(Level.FINE, this.rb.getResourceString("mdn.incoming", mdnInfo.getMessageId()));
            }
            throw new Exception(this.rb.getResourceString("mdn.unexpected.messageid",
                    new Object[] { mdnInfo.getMessageId(), mdnInfo.getRelatedMessageId() }));
        }
        //do not add an MDN to a message that is already ok or stopped
        int messageState = messageAccess.getMessageState(mdnInfo.getRelatedMessageId());
        if (messageState != AS2Message.STATE_PENDING) {
            throw new Exception(this.rb.getResourceString("mdn.unexpected.state",
                    new Object[] { mdnInfo.getMessageId(), mdnInfo.getRelatedMessageId() }));
        }
        mdnAccess.initializeOrUpdateMDN(mdnInfo);
        if (this.logger != null) {
            this.logger.log(Level.FINE, this.rb.getResourceString("mdn.incoming", mdnInfo.getMessageId()),
                    relatedMessageInfo);
        }
        relatedMessageInfo = messageAccess.getLastMessageEntry(mdnInfo.getRelatedMessageId());
        if (this.logger != null) {
            this.logger.log(Level.INFO,
                    this.rb.getResourceString("mdn.answerto",
                            new Object[] { mdnInfo.getMessageId(), mdnInfo.getRelatedMessageId() }),
                    relatedMessageInfo);
        }
        if (mdnParser.getDispositionState() != null) {
            //failure in processing message on remote AS2 server: log the failure and set transaction state
            if (mdnParser.getDispositionState().toLowerCase().indexOf("failed") >= 0
                    || mdnParser.getDispositionState().toLowerCase().indexOf("error") >= 0) {
                if (this.logger != null) {
                    this.logger.log(Level.SEVERE,
                            this.rb.getResourceString("mdn.state",
                                    new Object[] { mdnInfo.getMessageId(), mdnParser.getDispositionState() }),
                            relatedMessageInfo);
                    this.logger.log(Level.SEVERE,
                            this.rb.getResourceString("mdn.details",
                                    new Object[] { mdnInfo.getMessageId(), mdnParser.getMdnDetails() }),
                            relatedMessageInfo);
                }
                mdnInfo.setState(AS2Message.STATE_STOPPED);
                mdnInfo.setRemoteMDNText("[" + mdnParser.getDispositionState() + "] " + mdnParser.getMdnDetails());
                mdnAccess.initializeOrUpdateMDN(mdnInfo);
                relatedMessageInfo.setState(AS2Message.STATE_STOPPED);
                //update the related message in the database. This has to be performed because 
                //of the notification
                messageAccess.setMessageState(relatedMessageInfo.getMessageId(), AS2Message.STATE_STOPPED);
                messageAccess.initializeOrUpdateMessage(relatedMessageInfo);
            } else {
                //message has been processed: log the found state
                if (this.logger != null) {
                    this.logger.log(Level.FINE,
                            this.rb.getResourceString("mdn.state",
                                    new Object[] { mdnInfo.getMessageId(), mdnParser.getDispositionState() }),
                            relatedMessageInfo);
                    this.logger.log(Level.FINE,
                            this.rb.getResourceString("mdn.details",
                                    new Object[] { mdnInfo.getMessageId(), mdnParser.getMdnDetails() }),
                            relatedMessageInfo);
                }
                mdnInfo.setState(AS2Message.STATE_FINISHED);
                mdnInfo.setRemoteMDNText("[" + mdnParser.getDispositionState() + "] " + mdnParser.getMdnDetails());
                mdnAccess.initializeOrUpdateMDN(mdnInfo);
                //relatedMessageInfo.setState(AS2Message.STATE_FINISHED);
                messageAccess.initializeOrUpdateMessage(relatedMessageInfo);
            }
        }
        AS2Message message = new AS2Message(mdnInfo);
        message.setRawData(rawMessageData);
        //check for existing partners
        PartnerAccessDB partnerAccess = new PartnerAccessDB(this.configConnection, this.runtimeConnection);
        Partner sender = partnerAccess.getPartner(mdnInfo.getSenderId());
        Partner receiver = partnerAccess.getPartner(mdnInfo.getReceiverId());
        if (sender == null) {
            throw new AS2Exception(AS2Exception.UNKNOWN_TRADING_PARTNER_ERROR,
                    "Sender AS2 id " + mdnInfo.getSenderId() + " is unknown.", message);
        }
        if (receiver == null) {
            throw new AS2Exception(AS2Exception.UNKNOWN_TRADING_PARTNER_ERROR,
                    "Receiver AS2 id " + mdnInfo.getReceiverId() + " is unknown.", message);
        }
        if (!receiver.isLocalStation()) {
            throw new AS2Exception(AS2Exception.PROCESSING_ERROR, "The receiver of the message ("
                    + receiver.getAS2Identification() + ") is not defined as a local station.", message);
        }
        message.setDecryptedRawData(rawMessageData);
        Part payloadPart = this.verifySignature(message, sender, contentType);
        //is it a MDN and everything performed well up to here? Then perform the MIC check
        if (mdnInfo.getState() == AS2Message.STATE_FINISHED) {
            this.checkMDNReceivedContentMIC(mdnInfo);
        }
        //this is a signed MDN
        if (message.getAS2Info().getSignType() != AS2Message.SIGNATURE_NONE) {
            this.writePayloadsToMessage(payloadPart, message, header);
        } else {
            //this is an unsigned mdn
            this.writePayloadsToMessage(message.getDecryptedRawData(), message, header);
        }
        return (message);
    }

    /**Analyzes and creates passed message data
     */
    public AS2Message createMessageFromRequest(byte[] rawMessageData, Properties header, String contentType)
            throws AS2Exception {
        AS2Message message = new AS2Message(new AS2MessageInfo());
        if (this.configConnection == null) {
            throw new AS2Exception(AS2Exception.PROCESSING_ERROR,
                    "AS2MessageParser: Pass a DB connection before calling createMessageFromRequest()", message);
        }
        try {
            //decode the content transfer encoding if set
            rawMessageData = this.processContentTransferEncoding(rawMessageData, header);
            //check if this is a MDN
            MDNParser mdnParser = new MDNParser();
            AS2MDNInfo mdnInfo = mdnParser.parseMDNData(rawMessageData, contentType);
            //its a MDN
            if (mdnInfo != null) {
                message.setAS2Info(mdnInfo);
                mdnInfo.initializeByRequestHeader(header);
                return (this.createFromMDNRequest(rawMessageData, header, contentType, mdnInfo, mdnParser));
            } else {
                AS2MessageInfo messageInfo = (AS2MessageInfo) message.getAS2Info();
                messageInfo.initializeByRequestHeader(header);
                //inbound AS2 message, no MDN
                //no futher processing if the message does not contain a message id
                if (messageInfo.getMessageId() == null) {
                    return (message);
                }
                //figure out if the MDN should be signed NOW. If an error occurs beyond this point
                //the info has to know if the returned MDN should be signed
                messageInfo.getDispositionNotificationOptions()
                        .setHeaderValue(header.getProperty("disposition-notification-options"));
                //check for existing partners
                PartnerAccessDB partnerAccess = new PartnerAccessDB(this.configConnection, this.runtimeConnection);
                Partner sender = partnerAccess.getPartner(messageInfo.getSenderId());
                Partner receiver = partnerAccess.getPartner(messageInfo.getReceiverId());
                if (sender == null) {
                    throw new AS2Exception(AS2Exception.UNKNOWN_TRADING_PARTNER_ERROR,
                            "Sender AS2 id " + messageInfo.getSenderId() + " is unknown.", message);
                }
                if (receiver == null) {
                    throw new AS2Exception(AS2Exception.UNKNOWN_TRADING_PARTNER_ERROR,
                            "Receiver AS2 id " + messageInfo.getReceiverId() + " is unknown.", message);
                }
                if (!receiver.isLocalStation()) {
                    throw new AS2Exception(
                            AS2Exception.PROCESSING_ERROR, "The receiver of the message ("
                                    + receiver.getAS2Identification() + ") is not defined as a local station.",
                            message);
                }
                MessageAccessDB messageAccess = new MessageAccessDB(this.configConnection, this.runtimeConnection);
                //check if the message already exists
                AS2MessageInfo alreadyExistingInfo = this.messageAlreadyExists(messageAccess,
                        messageInfo.getMessageId());
                if (alreadyExistingInfo != null) {
                    //perform notification: Resend detected, manual interaction might be required
                    Notification notification = new Notification(this.configConnection, this.runtimeConnection);
                    notification.sendResendDetected(messageInfo, alreadyExistingInfo, sender, receiver);
                    throw new AS2Exception(AS2Exception.PROCESSING_ERROR, "An AS2 message with the message id "
                            + messageInfo.getMessageId()
                            + " has been already processed successfully by the system or is pending ("
                            + alreadyExistingInfo.getInitDate() + "). Please "
                            + " resubmit the message with a new message id instead or resending it if it should be processed again.",
                            new AS2Message(messageInfo));
                }
                messageAccess.initializeOrUpdateMessage(messageInfo);
                if (this.logger != null) {
                    //do not log before because the logging process is related to an already created message in the transaction log
                    this.logger.log(
                            Level.FINE, this.rb
                                    .getResourceString("msg.incoming",
                                            new Object[] { messageInfo.getMessageId(),
                                                    AS2Tools.getDataSizeDisplay(rawMessageData.length) }),
                            messageInfo);
                }
                //indicates if a sync or async mdn is requested
                messageInfo.setAsyncMDNURL(header.getProperty("receipt-delivery-option"));
                messageInfo.setRequestsSyncMDN(header.getProperty("receipt-delivery-option") == null
                        || header.getProperty("receipt-delivery-option").trim().length() == 0);
                message.setRawData(rawMessageData);
                byte[] decryptedData = this.decryptMessage(message, rawMessageData, contentType, sender, receiver);
                //may be already compressed here. Decompress first before going further
                if (this.contentTypeIndicatesCompression(contentType)) {
                    byte[] decompressed = this.decompressData((AS2MessageInfo) message.getAS2Info(), decryptedData,
                            contentType);
                    message.setDecryptedRawData(decompressed);
                    //content type has changed now, get it from the decompressed data
                    ByteArrayInputStream memIn = new ByteArrayInputStream(decompressed);
                    MimeBodyPart tempPart = new MimeBodyPart(memIn);
                    memIn.close();
                    contentType = tempPart.getContentType();
                } else {
                    //check the MIME structure that is embedded in decryptedData for its content type
                    ByteArrayInputStream memIn = new ByteArrayInputStream(decryptedData);
                    MimeMessage possibleCompressedPart = new MimeMessage(
                            Session.getInstance(System.getProperties()), memIn);
                    memIn.close();
                    if (this.contentTypeIndicatesCompression(possibleCompressedPart.getContentType())) {
                        long compressedSize = possibleCompressedPart.getSize();
                        byte[] decompressed = this.decompressData((AS2MessageInfo) message.getAS2Info(),
                                new SMIMECompressed(possibleCompressedPart), compressedSize);
                        message.setDecryptedRawData(decompressed);
                    } else {
                        message.setDecryptedRawData(decryptedData);
                    }
                }
                decryptedData = null;
                Part payloadPart = this.verifySignature(message, sender, contentType);
                //decompress the data if it has been sent compressed, only possible for signed data
                if (message.getAS2Info().getSignType() != AS2Message.SIGNATURE_NONE) {
                    //signed message:
                    //http://tools.ietf.org/html/draft-ietf-ediint-compression-12
                    //4.1 MIC Calculation For Signed Message
                    //For any signed message, the MIC to be returned is calculated over
                    //the same data that was signed in the original message as per [AS1].
                    //The signed content will be a mime bodypart that contains either
                    //compressed or uncompressed data.                    
                    this.computeReceivedContentMIC(rawMessageData, message, payloadPart, contentType);
                    payloadPart = this.decompressData(payloadPart, message);
                    this.writePayloadsToMessage(payloadPart, message, header);
                } else {
                    //this is an unsigned message
                    this.writePayloadsToMessage(message.getDecryptedRawData(), message, header);
                    //unsigned message:
                    //http://tools.ietf.org/html/draft-ietf-ediint-compression-12
                    //4.2 MIC Calculation For Encrypted, Unsigned Message
                    //For encrypted, unsigned messages, the MIC to be returned is
                    //calculated over the uncompressed data content including all
                    //MIME header fields and any applied Content-Transfer-Encoding.

                    //http://tools.ietf.org/html/draft-ietf-ediint-compression-12
                    //4.3 MIC Calculation For Unencrypted, Unsigned Message
                    //For unsigned, unencrypted messages, the MIC is calculated
                    //over the uncompressed data content including all MIME header
                    //fields and any applied Content-Transfer-Encoding.                    
                    this.computeReceivedContentMIC(rawMessageData, message, payloadPart, contentType);
                }
                if (this.logger != null) {
                    this.logger.log(Level.INFO, this.rb.getResourceString("found.attachments",
                            new Object[] { messageInfo.getMessageId(), String.valueOf(message.getPayloadCount()) }),
                            messageInfo);
                }
                return (message);
            }
        } catch (Exception e) {
            if (e instanceof AS2Exception) {
                throw (AS2Exception) e;
            } else {
                throw new AS2Exception(AS2Exception.PROCESSING_ERROR, e.getMessage(), message);
            }
        }
    }

    /**
     * Sending implementations MUST be capable of configuring a maximum number
    of retries, and MUST stop retrying either when a successful send
    occurs or when the total retry number is reached.
     * @return
     */
    private AS2MessageInfo messageAlreadyExists(MessageAccessDB messageAccess, String messageId) {
        List<AS2MessageInfo> infos = messageAccess.getMessageOverview(messageId);
        if (infos != null && !infos.isEmpty()) {
            return (infos.get(0));
        }
        return (null);
    }

    /**Checks if the received content mic matches the send message mic. The content mic field is optional if
     * the MDN is unsigned, see RFC 4130 section 7.4.3:
     * The "Received-content-MIC" extension field is set when the integrity of the received
     * message is verified. The MIC is the base64-encoded message-digest computed over the
     * received message with a hash function. This field is required for signed receipts but
     * optional for unsigned receipts.
     */
    private void checkMDNReceivedContentMIC(AS2MDNInfo mdnMessageInfo) throws AS2Exception {
        //ignore this check if the received content mic has not been set in the MDN and the MDN is unsigned
        if (mdnMessageInfo.getSignType() == AS2Message.SIGNATURE_NONE
                && mdnMessageInfo.getReceivedContentMIC() == null) {
            return;
        }
        MessageAccessDB messageAccess = new MessageAccessDB(this.configConnection, this.runtimeConnection);
        AS2MessageInfo relatedMessageInfo = messageAccess.getLastMessageEntry(mdnMessageInfo.getRelatedMessageId());
        BCCryptoHelper helper = new BCCryptoHelper();
        if (helper.micIsEqual(mdnMessageInfo.getReceivedContentMIC(), relatedMessageInfo.getReceivedContentMIC())) {
            if (this.logger != null) {
                this.logger.log(Level.INFO, this.rb.getResourceString("contentmic.match",
                        new Object[] { mdnMessageInfo.getMessageId() }), relatedMessageInfo);
            }
        } else {
            if (this.logger != null) {
                this.logger.log(Level.INFO,
                        this.rb.getResourceString("contentmic.failure",
                                new Object[] { mdnMessageInfo.getMessageId(),
                                        relatedMessageInfo.getReceivedContentMIC(),
                                        mdnMessageInfo.getReceivedContentMIC() }),
                        relatedMessageInfo);
            }
            //uncomment for ERROR if mic does not match
            //            throw new AS2Exception(AS2Exception.INTEGRITY_ERROR,
            //                    this.rb.getResourceString("contentmic.failure",
            //                    new Object[]{
            //                        mdnMessageInfo.getMessageId(),
            //                        relatedMessageInfo.getReceivedContentMIC(),
            //                        mdnMessageInfo.getReceivedContentMIC()
            //                    }), message);
        }
    }

    /**Writes a passed payload data to the passed message object. Could be called from either the MDN
     * processing or the message processing
     */
    public void writePayloadsToMessage(byte[] data, AS2Message message, Properties header) throws Exception {
        ByteArrayOutputStream payloadOut = new ByteArrayOutputStream();
        MimeMessage testMessage = new MimeMessage(Session.getInstance(System.getProperties()),
                new ByteArrayInputStream(data));
        //multiple attachments?
        if (testMessage.isMimeType("multipart/*")) {
            this.writePayloadsToMessage(testMessage, message, header);
            return;
        }
        InputStream payloadIn = null;
        AS2Info info = message.getAS2Info();
        if (info instanceof AS2MessageInfo && info.getSignType() == AS2Message.SIGNATURE_NONE
                && ((AS2MessageInfo) info).getCompressionType() == AS2Message.COMPRESSION_NONE) {
            payloadIn = new ByteArrayInputStream(data);
        } else if (testMessage.getSize() > 0) {
            payloadIn = testMessage.getInputStream();
        } else {
            payloadIn = new ByteArrayInputStream(data);
        }
        this.copyStreams(payloadIn, payloadOut);
        payloadOut.flush();
        payloadOut.close();
        byte[] payloadData = payloadOut.toByteArray();
        AS2Payload as2Payload = new AS2Payload();
        as2Payload.setData(payloadData);
        String contentIdHeader = header.getProperty("content-id");
        if (contentIdHeader != null) {
            as2Payload.setContentId(contentIdHeader);
        }
        String contentTypeHeader = header.getProperty("content-type");
        if (contentTypeHeader != null) {
            as2Payload.setContentType(contentTypeHeader);
        }
        try {
            as2Payload.setOriginalFilename(testMessage.getFileName());
        } catch (MessagingException e) {
            if (this.logger != null) {
                this.logger.log(Level.WARNING, this.rb.getResourceString("filename.extraction.error",
                        new Object[] { info.getMessageId(), e.getMessage(), }), info);
            }
        }
        if (as2Payload.getOriginalFilename() == null) {
            String filenameHeader = header.getProperty("content-disposition");
            if (filenameHeader != null) {
                //test part for convinience: extract file name
                MimeBodyPart filenamePart = new MimeBodyPart();
                filenamePart.setHeader("content-disposition", filenameHeader);
                try {
                    as2Payload.setOriginalFilename(filenamePart.getFileName());
                } catch (MessagingException e) {
                    if (this.logger != null) {
                        this.logger.log(Level.WARNING, this.rb.getResourceString("filename.extraction.error",
                                new Object[] { info.getMessageId(), e.getMessage(), }), info);
                    }
                }
            }
        }
        message.addPayload(as2Payload);
    }

    /**Writes a passed payload part to the passed message object. 
     */
    public void writePayloadsToMessage(Part payloadPart, AS2Message message, Properties header) throws Exception {
        List<Part> attachmentList = new ArrayList<Part>();
        AS2Info info = message.getAS2Info();
        if (!info.isMDN()) {
            AS2MessageInfo messageInfo = (AS2MessageInfo) message.getAS2Info();
            if (payloadPart.isMimeType("multipart/*")) {
                //check if it is a CEM
                if (payloadPart.getContentType().toLowerCase().contains("application/ediint-cert-exchange+xml")) {
                    messageInfo.setMessageType(AS2Message.MESSAGETYPE_CEM);
                    if (this.logger != null) {
                        this.logger.log(Level.FINE, this.rb.getResourceString("found.cem",
                                new Object[] { messageInfo.getMessageId(), message }), info);
                    }
                }
                ByteArrayOutputStream mem = new ByteArrayOutputStream();
                payloadPart.writeTo(mem);
                mem.flush();
                mem.close();
                MimeMultipart multipart = new MimeMultipart(
                        new ByteArrayDataSource(mem.toByteArray(), payloadPart.getContentType()));
                //add all attachments to the message
                for (int i = 0; i < multipart.getCount(); i++) {
                    //its possible that one of the bodyparts is the signature (for compressed/signed messages), skip the signature
                    if (!multipart.getBodyPart(i).getContentType().toLowerCase().contains("pkcs7-signature")) {
                        attachmentList.add(multipart.getBodyPart(i));
                    }
                }
            } else {
                attachmentList.add(payloadPart);
            }
        } else {
            //its a MDN, write whole part
            attachmentList.add(payloadPart);
        }
        //write the parts
        for (Part attachmentPart : attachmentList) {
            ByteArrayOutputStream payloadOut = new ByteArrayOutputStream();
            InputStream payloadIn = attachmentPart.getInputStream();
            this.copyStreams(payloadIn, payloadOut);
            payloadOut.flush();
            payloadOut.close();
            byte[] data = payloadOut.toByteArray();
            AS2Payload as2Payload = new AS2Payload();
            as2Payload.setData(data);
            String[] contentIdHeader = attachmentPart.getHeader("content-id");
            if (contentIdHeader != null && contentIdHeader.length > 0) {
                as2Payload.setContentId(contentIdHeader[0]);
            }
            String[] contentTypeHeader = attachmentPart.getHeader("content-type");
            if (contentTypeHeader != null && contentTypeHeader.length > 0) {
                as2Payload.setContentType(contentTypeHeader[0]);
            }
            try {
                as2Payload.setOriginalFilename(payloadPart.getFileName());
            } catch (MessagingException e) {
                if (this.logger != null) {
                    this.logger.log(Level.WARNING, this.rb.getResourceString("filename.extraction.error",
                            new Object[] { info.getMessageId(), e.getMessage(), }), info);
                }
            }
            if (as2Payload.getOriginalFilename() == null) {
                String filenameheader = header.getProperty("content-disposition");
                if (filenameheader != null) {
                    //test part for convinience: extract file name
                    MimeBodyPart filenamePart = new MimeBodyPart();
                    filenamePart.setHeader("content-disposition", filenameheader);
                    try {
                        as2Payload.setOriginalFilename(filenamePart.getFileName());
                    } catch (MessagingException e) {
                        if (this.logger != null) {
                            this.logger.log(Level.WARNING, this.rb.getResourceString("filename.extraction.error",
                                    new Object[] { info.getMessageId(), e.getMessage(), }), info);
                        }
                    }
                }
            }
            message.addPayload(as2Payload);
        }
    }

    /**Computes the received content MIC and writes it to the message info object
     */
    public void computeReceivedContentMIC(byte[] rawMessageData, AS2Message message, Part partWithHeader,
            String contentType) throws Exception {
        AS2MessageInfo messageInfo = (AS2MessageInfo) message.getAS2Info();
        boolean encrypted = messageInfo.getEncryptionType() != AS2Message.ENCRYPTION_NONE;
        boolean signed = messageInfo.getSignType() != AS2Message.SIGNATURE_NONE;
        boolean compressed = messageInfo.getCompressionType() != AS2Message.COMPRESSION_NONE;
        BCCryptoHelper helper = new BCCryptoHelper();
        String sha1digestOID = helper.convertAlgorithmNameToOID(BCCryptoHelper.ALGORITHM_SHA1);
        //compute the MIC
        if (signed) {
            //compute the content-type for the signed part.
            //If the message was not encrypted the content-type should simply be taken from the header
            //else we have to look into the part
            String singedPartContentType = null;
            if (!encrypted) {
                singedPartContentType = contentType;
            } else {
                InputStream dataIn = message.getDecryptedRawDataInputStream();
                MimeBodyPart contentTypeTempPart = new MimeBodyPart(dataIn);
                dataIn.close();
                singedPartContentType = contentTypeTempPart.getContentType();
            }
            //ANY signed data
            //4.1 MIC Calculation For Signed Message
            //For any signed message, the MIC to be returned is calculated over
            //the same data that was signed in the original message as per [AS1].
            //The signed content will be a mime bodypart that contains either
            //compressed or uncompressed data.
            MimeBodyPart signedPart = new MimeBodyPart();
            signedPart.setDataHandler(
                    new DataHandler(new ByteArrayDataSource(message.getDecryptedRawData(), contentType)));
            signedPart.setHeader("Content-Type", singedPartContentType);
            String digestOID = helper.getDigestAlgOIDFromSignature(signedPart);
            signedPart = null;
            String mic = helper.calculateMIC(partWithHeader, digestOID);
            String digestAlgorithmName = helper.convertOIDToAlgorithmName(digestOID);
            messageInfo.setReceivedContentMIC(mic + ", " + digestAlgorithmName);
        } else if (!signed && !compressed && !encrypted) {
            //uncompressed, unencrypted, unsigned: plaintext mic
            //http://tools.ietf.org/html/draft-ietf-ediint-compression-12
            //4.3 MIC Calculation For Unencrypted, Unsigned Message
            //For unsigned, unencrypted messages, the MIC is calculated
            //over the uncompressed data content including all MIME header
            //fields and any applied Content-Transfer-Encoding.
            String mic = helper.calculateMIC(rawMessageData, sha1digestOID);
            messageInfo.setReceivedContentMIC(mic + ", sha1");
        } else if (!signed && compressed && !encrypted) {
            //compressed, unencrypted, unsigned: uncompressed data mic
            //http://tools.ietf.org/html/draft-ietf-ediint-compression-12
            //4.3 MIC Calculation For Unencrypted, Unsigned Message
            //For unsigned, unencrypted messages, the MIC is calculated
            //over the uncompressed data content including all MIME header
            //fields and any applied Content-Transfer-Encoding.
            String mic = helper.calculateMIC(message.getDecryptedRawData(), sha1digestOID);
            messageInfo.setReceivedContentMIC(mic + ", sha1");
        } else if (!signed && encrypted) {
            //http://tools.ietf.org/html/draft-ietf-ediint-compression-12
            //4.2 MIC Calculation For Encrypted, Unsigned Message
            //For encrypted, unsigned messages, the MIC to be returned is
            //calculated over the uncompressed data content including all
            //MIME header fields and any applied Content-Transfer-Encoding.
            String mic = helper.calculateMIC(message.getDecryptedRawData(), sha1digestOID);
            messageInfo.setReceivedContentMIC(mic + ", sha1");
        } else {
            //this should never happen:
            String mic = helper.calculateMIC(partWithHeader, sha1digestOID);
            messageInfo.setReceivedContentMIC(mic + ", sha1");
        }
    }

    /**Returns a compressed part of this container if it exists, else null. If the container itself
     *is compressed it is returned.
     */
    public Part getCompressedEmbeddedPart(Part part) throws MessagingException, IOException {
        if (this.contentTypeIndicatesCompression(part.getContentType())) {
            return (part);
        }
        if (part.isMimeType("multipart/*")) {
            Multipart multiPart = (Multipart) part.getContent();
            int count = multiPart.getCount();
            for (int i = 0; i < count; i++) {
                BodyPart bodyPart = multiPart.getBodyPart(i);
                Part compressedEmbeddedPart = this.getCompressedEmbeddedPart(bodyPart);
                if (compressedEmbeddedPart != null) {
                    return (compressedEmbeddedPart);
                }
            }
        }
        return (null);
    }

    /**Returns the signed part of the passed data or null if the data is not detected to be signed*/
    public Part getSignedPart(byte[] data, String contentType) throws Exception {
        BCCryptoHelper helper = new BCCryptoHelper();
        MimeBodyPart possibleSignedPart = new MimeBodyPart();
        possibleSignedPart.setDataHandler(new DataHandler(new ByteArrayDataSource(data, contentType)));
        possibleSignedPart.setHeader("content-type", contentType);
        return (helper.getSignedEmbeddedPart(possibleSignedPart));
    }

    /**Verifies the signature of the passed signed part*/
    public MimeBodyPart verifySignedPart(Part signedPart, byte[] data, String contentType,
            X509Certificate certificate) throws Exception {
        BCCryptoHelper helper = new BCCryptoHelper();
        String signatureTransferEncoding = null;
        MimeMultipart checkPart = (MimeMultipart) signedPart.getContent();
        //it is sure that it is a signed part: set the type to multipart if the
        //parser has problems parsing it. Don't know why sometimes a parsing fails for
        //MimeBodyPart. This check looks if the parser is able to find more than one subpart
        if (checkPart.getCount() == 1) {
            MimeMultipart multipart = new MimeMultipart(new ByteArrayDataSource(data, contentType));
            MimeMessage possibleSignedMessage = new MimeMessage(Session.getInstance(System.getProperties(), null));
            possibleSignedMessage.setContent(multipart, multipart.getContentType());
            possibleSignedMessage.saveChanges();
            //overwrite the formerly found signed part
            signedPart = helper.getSignedEmbeddedPart(possibleSignedMessage);
        }
        //get the content encoding of the signature
        MimeMultipart signedMultiPart = (MimeMultipart) signedPart.getContent();
        //body part 1 is always the signature
        String encodingHeader[] = signedMultiPart.getBodyPart(1).getHeader("Content-Transfer-Encoding");
        if (encodingHeader != null) {
            signatureTransferEncoding = encodingHeader[0];
        }
        return (helper.verify(signedPart, signatureTransferEncoding, certificate));
    }

    /**Verifies the signature of the passed message. If the transfer mode is unencrypted/unsigned, a new Bodypart will be constructed
     *@return the payload part, this is important to compute the MIC later
     */
    private Part verifySignature(AS2Message message, Partner sender, String contentType) throws Exception {
        if (this.certificateManagerSignature == null) {
            throw new AS2Exception(AS2Exception.PROCESSING_ERROR,
                    "AS2MessageParser.verifySignature: pass a certification manager for the signature before calling verifySignature()",
                    message);
        }
        AS2Info as2Info = message.getAS2Info();
        if (!as2Info.isMDN()) {
            AS2MessageInfo messageInfo = (AS2MessageInfo) as2Info;
            if (messageInfo.getEncryptionType() != AS2Message.ENCRYPTION_NONE) {
                InputStream memIn = message.getDecryptedRawDataInputStream();
                MimeBodyPart testPart = new MimeBodyPart(memIn);
                memIn.close();
                contentType = testPart.getContentType();
            }
        }
        Part signedPart = this.getSignedPart(message.getDecryptedRawData(), contentType);
        //part is NOT signed but is defined to be signed
        if (signedPart == null) {
            as2Info.setSignType(AS2Message.SIGNATURE_NONE);
            if (as2Info.isMDN()) {
                this.logger.log(Level.INFO, this.rb.getResourceString("mdn.notsigned", as2Info.getMessageId()),
                        as2Info);
                //MDN is not signed but should be signed
                if (sender.isSignedMDN()) {
                    this.logger.log(Level.SEVERE, this.rb.getResourceString("mdn.unsigned.error",
                            new Object[] { as2Info.getMessageId(), sender.getName(), }), as2Info);
                }
            } else {
                this.logger.log(Level.INFO, this.rb.getResourceString("msg.notsigned", as2Info.getMessageId()),
                        as2Info);
            }
            if (!as2Info.isMDN() && sender.getSignType() != AS2Message.SIGNATURE_NONE) {
                throw new AS2Exception(AS2Exception.INSUFFICIENT_SECURITY_ERROR,
                        "Incoming messages from AS2 partner " + sender.getAS2Identification()
                                + " are defined to be signed.",
                        message);
            }
            //if the message has been unsigned it is required to set a new datasource
            MimeBodyPart unsignedPart = new MimeBodyPart();
            unsignedPart.setDataHandler(
                    new DataHandler(new ByteArrayDataSource(message.getDecryptedRawData(), contentType)));
            unsignedPart.setHeader("content-type", contentType);
            return (unsignedPart);
        } else {
            //it is definitly a signed mdn
            if (as2Info.isMDN()) {
                if (this.logger != null) {
                    this.logger.log(Level.INFO, this.rb.getResourceString("mdn.signed", as2Info.getMessageId()),
                            as2Info);
                }
                as2Info.setSignType(this.getDigestFromSignature(signedPart));
                //MDN is signed but shouldn't be signed'
                if (!sender.isSignedMDN()) {
                    if (this.logger != null) {
                        this.logger.log(Level.WARNING, this.rb.getResourceString("mdn.signed.error",
                                new Object[] { as2Info.getMessageId(), sender.getName(), }), as2Info);
                    }
                }
            } else {
                //its no MDN, its a AS2 message
                if (this.logger != null) {
                    this.logger.log(Level.INFO, this.rb.getResourceString("msg.signed", as2Info.getMessageId()),
                            as2Info);
                }
                int signDigest = this.getDigestFromSignature(signedPart);
                String digest = null;
                if (signDigest == AS2Message.SIGNATURE_SHA1) {
                    digest = "SHA1";
                } else if (signDigest == AS2Message.SIGNATURE_MD5) {
                    digest = "MD5";
                }
                as2Info.setSignType(signDigest);
                if (this.logger != null) {
                    this.logger.log(Level.INFO, this.rb.getResourceString("signature.analyzed.digest",
                            new Object[] { as2Info.getMessageId(), digest }), as2Info);
                }
            }
        }
        MimeBodyPart payloadPart = null;
        try {
            String signAlias = this.certificateManagerSignature
                    .getAliasByFingerprint(sender.getSignFingerprintSHA1());
            payloadPart = this.verifySignedPartUsingAlias(message, signAlias, signedPart, contentType);
        } catch (AS2Exception e) {
            //retry to verify the signature with the second certificate if this is possible
            String secondAlias = this.certificateManagerSignature
                    .getAliasByFingerprint(sender.getSignFingerprintSHA1(2));
            if (secondAlias != null) {
                payloadPart = this.verifySignedPartUsingAlias(message, secondAlias, signedPart, contentType);
            } else {
                throw e;
            }
        }
        return (payloadPart);
    }

    private MimeBodyPart verifySignedPartUsingAlias(AS2Message message, String alias, Part signedPart,
            String contentType) throws Exception {
        AS2Info info = message.getAS2Info();
        if (this.logger != null) {
            this.logger.log(Level.INFO,
                    this.rb.getResourceString("signature.using.alias", new Object[] { info.getMessageId(), alias }),
                    info);
        }
        X509Certificate certificate = this.certificateManagerSignature.getX509Certificate(alias);
        MimeBodyPart payloadPart = null;
        try {
            payloadPart = this.verifySignedPart(signedPart, message.getDecryptedRawData(), contentType,
                    certificate);
        } catch (Exception e) {
            if (this.logger != null) {
                this.logger.log(Level.INFO, this.rb.getResourceString("signature.failure",
                        new Object[] { info.getMessageId(), e.getMessage() }), info);
            }
            throw new AS2Exception(AS2Exception.AUTHENTIFICATION_ERROR,
                    "Error verifying the senders digital signature: " + e.getMessage() + ".", message);
        }
        if (this.logger != null) {
            this.logger.log(Level.INFO, this.rb.getResourceString("signature.ok", info.getMessageId()), info);
        }
        return (payloadPart);
    }

    /*Returns the digest of the signature, as constant of AS2Message
     */
    public int getDigestFromSignature(Part signedPart) throws Exception {
        BCCryptoHelper helper = new BCCryptoHelper();
        String digestOID = helper.getDigestAlgOIDFromSignature(signedPart);
        String as2Digest = helper.convertOIDToAlgorithmName(digestOID);
        if (as2Digest.equalsIgnoreCase(BCCryptoHelper.ALGORITHM_SHA1)) {
            return (AS2Message.SIGNATURE_SHA1);
        }
        if (as2Digest.equalsIgnoreCase(BCCryptoHelper.ALGORITHM_MD5)) {
            return (AS2Message.SIGNATURE_MD5);
        }
        //should never happen because unknown algorithms are thrown already by the conversion method
        return (-1);
    }

    /**Returns if the content type indicates a message encryption*/
    public boolean contentTypeIndicatesEncryption(String contentType) {
        return (contentType.toLowerCase().contains("application/pkcs7-mime"));
    }

    /**Returns if the content type indicates message compression*/
    public boolean contentTypeIndicatesCompression(String contentType) {
        return (contentType.toLowerCase().contains("compressed-data"));
    }

    /**Decrypts the data of a message with all given certificates etc
     * @param info MessageInfo, the encryption algorith will be stored in the encryption type of this info
     * @param rawMessageData encrypted data, will be decrypted
     * @param contentType contentType of the data
     * @param privateKey receivers private key
     * @param certificate receivers certificate
     */
    public byte[] decryptData(AS2Message message, byte[] data, String contentType, PrivateKey privateKeyReceiver,
            X509Certificate certificateReceiver, String receiverCryptAlias) throws Exception {
        AS2MessageInfo info = (AS2MessageInfo) message.getAS2Info();
        MimeBodyPart encryptedBody = new MimeBodyPart();
        encryptedBody.setHeader("content-type", contentType);
        encryptedBody.setDataHandler(new DataHandler(new ByteArrayDataSource(data, contentType)));
        RecipientId recipientId = new JceKeyTransRecipientId(certificateReceiver);
        SMIMEEnveloped enveloped = new SMIMEEnveloped(encryptedBody);
        BCCryptoHelper helper = new BCCryptoHelper();
        String algorithm = helper.convertOIDToAlgorithmName(enveloped.getEncryptionAlgOID());
        if (algorithm.equals(BCCryptoHelper.ALGORITHM_3DES)) {
            info.setEncryptionType(AS2Message.ENCRYPTION_3DES);
        } else if (algorithm.equals(BCCryptoHelper.ALGORITHM_DES)) {
            info.setEncryptionType(AS2Message.ENCRYPTION_DES);
        } else if (algorithm.equals(BCCryptoHelper.ALGORITHM_RC2)) {
            info.setEncryptionType(AS2Message.ENCRYPTION_RC2_UNKNOWN);
        } else if (algorithm.equals(BCCryptoHelper.ALGORITHM_AES_128)) {
            info.setEncryptionType(AS2Message.ENCRYPTION_AES_128);
        } else if (algorithm.equals(BCCryptoHelper.ALGORITHM_AES_192)) {
            info.setEncryptionType(AS2Message.ENCRYPTION_AES_192);
        } else if (algorithm.equals(BCCryptoHelper.ALGORITHM_AES_256)) {
            info.setEncryptionType(AS2Message.ENCRYPTION_AES_256);
        } else if (algorithm.equals(BCCryptoHelper.ALGORITHM_RC4)) {
            info.setEncryptionType(AS2Message.ENCRYPTION_RC4_UNKNOWN);
        } else {
            info.setEncryptionType(AS2Message.ENCRYPTION_UNKNOWN_ALGORITHM);
        }
        RecipientInformationStore recipients = enveloped.getRecipientInfos();
        enveloped = null;
        encryptedBody = null;
        RecipientInformation recipient = recipients.get(recipientId);
        if (recipient == null) {
            //give some details about the required and used cert for the decryption
            Collection recipientList = recipients.getRecipients();
            Iterator iterator = recipientList.iterator();
            while (iterator.hasNext()) {
                RecipientInformation recipientInfo = (RecipientInformation) iterator.next();
                if (this.logger != null) {
                    this.logger.log(Level.SEVERE, this.rb.getResourceString("decryption.inforequired",
                            new Object[] { info.getMessageId(), recipientInfo.getRID() }), info);
                }
            }
            if (this.logger != null) {
                this.logger.log(Level.SEVERE, this.rb.getResourceString("decryption.infoassigned",
                        new Object[] { info.getMessageId(), receiverCryptAlias, recipientId }), info);
            }
            throw new AS2Exception(AS2Exception.AUTHENTIFICATION_ERROR,
                    "Error decrypting the message: Recipient certificate does not match.", message);
        }
        //Streamed decryption. Its also possible to use in memory decryption using getContent but that uses
        //far more memory.
        InputStream contentStream = recipient
                .getContentStream(new JceKeyTransEnvelopedRecipient(privateKeyReceiver).setProvider("BC"))
                .getContentStream();
        //InputStream contentStream = recipient.getContentStream(privateKeyReceiver, "BC").getContentStream();
        //threshold set to 20 MB: if the data is less then 20MB perform the operaion in memory else stream to disk
        DeferredFileOutputStream decryptedOutput = new DeferredFileOutputStream(20 * 1024 * 1024, "as2decryptdata_",
                ".mem", null);
        this.copyStreams(contentStream, decryptedOutput);
        decryptedOutput.flush();
        decryptedOutput.close();
        contentStream.close();
        byte[] decryptedData = null;
        //size of the data was < than the threshold
        if (decryptedOutput.isInMemory()) {
            decryptedData = decryptedOutput.getData();
        } else {
            //data has been written to a temp file: reread and return
            ByteArrayOutputStream memOut = new ByteArrayOutputStream();
            decryptedOutput.writeTo(memOut);
            memOut.flush();
            memOut.close();
            //finally delete the temp file
            boolean deleted = decryptedOutput.getFile().delete();
            decryptedData = memOut.toByteArray();
        }
        if (this.logger != null) {
            this.logger.log(Level.INFO,
                    this.rb.getResourceString("decryption.done.alias",
                            new Object[] { info.getMessageId(), receiverCryptAlias,
                                    this.rbMessage.getResourceString("encryption." + info.getEncryptionType()) }),
                    info);
        }
        return (decryptedData);
    }

    /**Decrypts the passed data and returns it. Will return the original data
     *if it is not marked as encrypted
     */
    private byte[] decryptMessage(AS2Message message, byte[] data, String contentType, Partner sender,
            Partner receiver) throws AS2Exception {
        if (this.certificateManagerEncryption == null) {
            throw new AS2Exception(AS2Exception.PROCESSING_ERROR,
                    "AS2MessageParser.decryptMessage: pass a certification manager for the encryption before calling decryptMessage()",
                    message);
        }
        try {
            AS2MessageInfo info = (AS2MessageInfo) message.getAS2Info();
            //first check if the message is decrypted.
            if (!this.contentTypeIndicatesEncryption(contentType)
                    || this.contentTypeIndicatesCompression(contentType)) {

                if (this.logger != null) {
                    this.logger.log(Level.INFO, this.rb.getResourceString("msg.notencrypted", info.getMessageId()),
                            info);
                }
                info.setEncryptionType(AS2Message.ENCRYPTION_NONE);
                //encryption expected?
                if (sender.getEncryptionType() != AS2Message.ENCRYPTION_NONE) {
                    throw new AS2Exception(AS2Exception.INSUFFICIENT_SECURITY_ERROR,
                            "Incoming messages from AS2 partner " + sender.getAS2Identification()
                                    + " are defined to be encrypted.",
                            message);
                }
                return (data);
            }
            if (this.logger != null) {
                this.logger.log(Level.INFO, this.rb.getResourceString("msg.encrypted", info.getMessageId()), info);
            }
            try {
                String cryptAlias = this.certificateManagerEncryption
                        .getAliasByFingerprint(receiver.getCryptFingerprintSHA1());
                X509Certificate certificate = this.certificateManagerEncryption.getX509Certificate(cryptAlias);
                //receiver priv key
                PrivateKey privateKey = this.certificateManagerEncryption.getPrivateKey(cryptAlias);
                return (this.decryptData(message, data, contentType, privateKey, certificate, cryptAlias));
            } catch (Exception e) {
                //fallback: try second certificate if it exists
                String cryptAlias = this.certificateManagerEncryption
                        .getAliasByFingerprint(receiver.getCryptFingerprintSHA1(2));
                if (cryptAlias != null) {
                    X509Certificate certificate = this.certificateManagerEncryption.getX509Certificate(cryptAlias);
                    //receiver priv key
                    PrivateKey privateKey = this.certificateManagerEncryption.getPrivateKey(cryptAlias);
                    return (this.decryptData(message, data, contentType, privateKey, certificate, cryptAlias));
                } else {
                    throw e;
                }
            }
        } catch (AS2Exception e) {
            throw e;
        } catch (Throwable e) {
            e.printStackTrace();
            throw new AS2Exception(AS2Exception.DECRYPTION_ERROR, e.getMessage(), message);
        }
    }

    /**Looks if the data is compressed and decompresses it if necessary
     */
    public Part decompressData(Part part, AS2Message message) throws Exception {
        Part compressedPart = this.getCompressedEmbeddedPart(part);
        if (compressedPart == null) {
            return (part);
        }
        SMIMECompressed compressed = null;
        if (compressedPart instanceof MimeBodyPart) {
            compressed = new SMIMECompressed((MimeBodyPart) compressedPart);
        } else {
            compressed = new SMIMECompressed((MimeMessage) compressedPart);
        }
        byte[] decompressedData = compressed.getContent();
        ((AS2MessageInfo) message.getAS2Info()).setCompressionType(AS2Message.COMPRESSION_ZLIB);
        if (this.logger != null) {
            this.logger.log(Level.INFO,
                    this.rb.getResourceString("data.compressed.expanded",
                            new Object[] { message.getAS2Info().getMessageId(),
                                    AS2Tools.getDataSizeDisplay(part.getSize()),
                                    AS2Tools.getDataSizeDisplay(decompressedData.length) }),
                    message.getAS2Info());
        }
        ByteArrayInputStream memIn = new ByteArrayInputStream(decompressedData);
        MimeBodyPart uncompressedPayload = new MimeBodyPart(memIn);
        memIn.close();
        return (uncompressedPayload);
    }

    /**Copies all data from one stream to another*/
    private void copyStreams(InputStream in, OutputStream out) throws IOException {
        BufferedInputStream inStream = new BufferedInputStream(in);
        BufferedOutputStream outStream = new BufferedOutputStream(out);
        //copy the contents to an output stream
        byte[] buffer = new byte[2048];
        int read = 0;
        //a read of 0 must be allowed, sometimes it takes time to
        //extract data from the input
        while (read != -1) {
            read = inStream.read(buffer);
            if (read > 0) {
                outStream.write(buffer, 0, read);
            }
        }
        outStream.flush();
    }
}