
com.helger.as2lib.processor.receiver.net.AS2ReceiverHandler Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of as2-lib Show documentation
Show all versions of as2-lib Show documentation
Open AS2 fork - library part
/**
* The FreeBSD Copyright
* Copyright 1994-2008 The FreeBSD Project. All rights reserved.
* Copyright (C) 2013-2016 Philip Helger philip[at]helger[dot]com
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE FREEBSD PROJECT ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FREEBSD PROJECT OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* The views and conclusions contained in the software and documentation
* are those of the authors and should not be interpreted as representing
* official policies, either expressed or implied, of the FreeBSD Project.
*/
package com.helger.as2lib.processor.receiver.net;
import java.net.HttpURLConnection;
import java.net.Socket;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.mail.MessagingException;
import javax.mail.internet.ContentType;
import javax.mail.internet.InternetHeaders;
import javax.mail.internet.MimeBodyPart;
import org.bouncycastle.cms.jcajce.ZlibExpanderProvider;
import org.bouncycastle.mail.smime.SMIMECompressed;
import org.bouncycastle.mail.smime.SMIMEUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.helger.as2lib.cert.ECertificatePartnershipType;
import com.helger.as2lib.cert.ICertificateFactory;
import com.helger.as2lib.crypto.ICryptoHelper;
import com.helger.as2lib.disposition.DispositionException;
import com.helger.as2lib.disposition.DispositionType;
import com.helger.as2lib.exception.OpenAS2Exception;
import com.helger.as2lib.exception.WrappedOpenAS2Exception;
import com.helger.as2lib.message.AS2Message;
import com.helger.as2lib.message.IMessage;
import com.helger.as2lib.message.IMessageMDN;
import com.helger.as2lib.params.MessageParameters;
import com.helger.as2lib.processor.CNetAttribute;
import com.helger.as2lib.processor.NoModuleException;
import com.helger.as2lib.processor.receiver.AS2ReceiverModule;
import com.helger.as2lib.processor.receiver.AbstractActiveNetModule;
import com.helger.as2lib.processor.sender.IProcessorSenderModule;
import com.helger.as2lib.processor.storage.IProcessorStorageModule;
import com.helger.as2lib.session.ComponentNotFoundException;
import com.helger.as2lib.session.IAS2Session;
import com.helger.as2lib.util.AS2Helper;
import com.helger.as2lib.util.CAS2Header;
import com.helger.as2lib.util.IOHelper;
import com.helger.as2lib.util.http.AS2HttpResponseHandlerSocket;
import com.helger.as2lib.util.http.AS2InputStreamProviderSocket;
import com.helger.as2lib.util.http.HTTPHelper;
import com.helger.as2lib.util.http.IAS2HttpResponseHandler;
import com.helger.commons.ValueEnforcer;
import com.helger.commons.io.stream.NonBlockingByteArrayOutputStream;
import com.helger.commons.io.stream.StreamHelper;
import com.helger.commons.lang.StackTraceHelper;
import com.helger.commons.state.ETriState;
import com.helger.commons.timing.StopWatch;
import com.helger.mail.datasource.ByteArrayDataSource;
public class AS2ReceiverHandler extends AbstractReceiverHandler
{
private static final Logger s_aLogger = LoggerFactory.getLogger (AS2ReceiverHandler.class);
private final AS2ReceiverModule m_aReceiverModule;
public AS2ReceiverHandler (@Nonnull final AS2ReceiverModule aModule)
{
m_aReceiverModule = ValueEnforcer.notNull (aModule, "Module");
}
@Nonnull
protected final AS2ReceiverModule getReceiverModule ()
{
return m_aReceiverModule;
}
/**
* Create a new message and record the source ip and port
*
* @param aSocket
* The socket through which the message will be read.
* @return The {@link AS2Message} to use and never null
.
*/
@Nonnull
protected AS2Message createMessage (@Nonnull final Socket aSocket)
{
final AS2Message aMsg = new AS2Message ();
aMsg.setAttribute (CNetAttribute.MA_SOURCE_IP, aSocket.getInetAddress ().toString ());
aMsg.setAttribute (CNetAttribute.MA_SOURCE_PORT, Integer.toString (aSocket.getPort ()));
aMsg.setAttribute (CNetAttribute.MA_DESTINATION_IP, aSocket.getLocalAddress ().toString ());
aMsg.setAttribute (CNetAttribute.MA_DESTINATION_PORT, Integer.toString (aSocket.getLocalPort ()));
aMsg.setAttribute (AS2Message.ATTRIBUTE_RECEIVED, Boolean.TRUE.toString ());
return aMsg;
}
protected void decrypt (@Nonnull final IMessage aMsg) throws OpenAS2Exception
{
final ICertificateFactory aCertFactory = m_aReceiverModule.getSession ().getCertificateFactory ();
final ICryptoHelper aCryptoHelper = AS2Helper.getCryptoHelper ();
try
{
final boolean bDisableDecrypt = aMsg.getPartnership ().isDisableDecrypt ();
final boolean bMsgIsEncrypted = aCryptoHelper.isEncrypted (aMsg.getData ());
final boolean bForceDecrypt = aMsg.getPartnership ().isForceDecrypt ();
if (bMsgIsEncrypted && bDisableDecrypt)
{
s_aLogger.info ("Message claims to be encrypted but decryption is disabled" + aMsg.getLoggingText ());
}
else
if (bMsgIsEncrypted || bForceDecrypt)
{
// Decrypt
if (bForceDecrypt && !bMsgIsEncrypted)
s_aLogger.info ("Forced decrypting" + aMsg.getLoggingText ());
else
if (s_aLogger.isDebugEnabled ())
s_aLogger.debug ("Decrypting" + aMsg.getLoggingText ());
final X509Certificate aReceiverCert = aCertFactory.getCertificate (aMsg,
ECertificatePartnershipType.RECEIVER);
final PrivateKey aReceiverKey = aCertFactory.getPrivateKey (aMsg, aReceiverCert);
final MimeBodyPart aDecryptedData = aCryptoHelper.decrypt (aMsg.getData (),
aReceiverCert,
aReceiverKey,
bForceDecrypt);
aMsg.setData (aDecryptedData);
// Remember that message was encrypted
aMsg.setAttribute (AS2Message.ATTRIBUTE_RECEIVED_ENCRYPTED, Boolean.TRUE.toString ());
s_aLogger.info ("Successfully decrypted incoming AS2 message" + aMsg.getLoggingText ());
}
}
catch (final Exception ex)
{
s_aLogger.error ("Error decrypting " + aMsg.getLoggingText () + ": " + ex.getMessage ());
throw new DispositionException (DispositionType.createError ("decryption-failed"),
AbstractActiveNetModule.DISP_DECRYPTION_ERROR,
ex);
}
}
protected void verify (@Nonnull final IMessage aMsg) throws OpenAS2Exception
{
final ICertificateFactory aCertFactory = m_aReceiverModule.getSession ().getCertificateFactory ();
final ICryptoHelper aCryptoHelper = AS2Helper.getCryptoHelper ();
try
{
final boolean bDisableVerify = aMsg.getPartnership ().isDisableVerify ();
final boolean bMsgIsSigned = aCryptoHelper.isSigned (aMsg.getData ());
final boolean bForceVerify = aMsg.getPartnership ().isForceVerify ();
if (bMsgIsSigned && bDisableVerify)
{
s_aLogger.info ("Message claims to be signed but signature validation is disabled" + aMsg.getLoggingText ());
}
else
if (bMsgIsSigned || bForceVerify)
{
if (bForceVerify && !bMsgIsSigned)
s_aLogger.info ("Forced verify signature" + aMsg.getLoggingText ());
else
if (s_aLogger.isDebugEnabled ())
s_aLogger.debug ("Verifying signature" + aMsg.getLoggingText ());
final X509Certificate aSenderCert = aCertFactory.getCertificateOrNull (aMsg,
ECertificatePartnershipType.SENDER);
boolean bUseCertificateInBodyPart;
final ETriState eUseCertificateInBodyPart = aMsg.getPartnership ().getVerifyUseCertificateInBodyPart ();
if (eUseCertificateInBodyPart.isDefined ())
{
// Use per partnership
bUseCertificateInBodyPart = eUseCertificateInBodyPart.getAsBooleanValue ();
}
else
{
// Use global value
bUseCertificateInBodyPart = m_aReceiverModule.getSession ().isCryptoVerifyUseCertificateInBodyPart ();
}
final MimeBodyPart aVerifiedData = aCryptoHelper.verify (aMsg.getData (),
aSenderCert,
bUseCertificateInBodyPart,
bForceVerify);
aMsg.setData (aVerifiedData);
// Remember that message was signed and verified
aMsg.setAttribute (AS2Message.ATTRIBUTE_RECEIVED_SIGNED, Boolean.TRUE.toString ());
s_aLogger.info ("Successfully verified signature of incoming AS2 message" + aMsg.getLoggingText ());
}
}
catch (final Exception ex)
{
s_aLogger.error ("Error verifying signature " + aMsg.getLoggingText () + ": " + ex.getMessage ());
throw new DispositionException (DispositionType.createError ("integrity-check-failed"),
AbstractActiveNetModule.DISP_VERIFY_SIGNATURE_FAILED,
ex);
}
}
protected void decompress (@Nonnull final IMessage aMsg) throws DispositionException
{
try
{
if (aMsg.getPartnership ().isDisableDecompress ())
{
s_aLogger.info ("Message claims to be compressed but decompression is disabled" + aMsg.getLoggingText ());
}
else
{
if (s_aLogger.isDebugEnabled ())
s_aLogger.debug ("Decompressing a compressed AS2 message");
final SMIMECompressed aCompressed = new SMIMECompressed (aMsg.getData ());
// decompression step MimeBodyPart
final MimeBodyPart aDecompressedPart = SMIMEUtil.toMimeBodyPart (aCompressed.getContent (new ZlibExpanderProvider ()));
// Update the message object
aMsg.setData (aDecompressedPart);
// Remember that message was decompressed
aMsg.setAttribute (AS2Message.ATTRIBUTE_RECEIVED_COMPRESSED, Boolean.TRUE.toString ());
s_aLogger.info ("Successfully decompressed incoming AS2 message" + aMsg.getLoggingText ());
}
}
catch (final Exception ex)
{
s_aLogger.error ("Error decompressing received message", ex);
throw new DispositionException (DispositionType.createError ("unexpected-processing-error"),
AbstractActiveNetModule.DISP_DECOMPRESSION_ERROR,
ex);
}
}
protected void sendSyncMDN (@Nonnull final String sClientInfo,
@Nonnull final IAS2HttpResponseHandler aResponseHandler,
@Nonnull final AS2Message aMsg,
@Nonnull final DispositionType aDisposition,
@Nonnull final String sText)
{
final boolean bMDNBlocked = aMsg.getPartnership ().isBlockErrorMDN ();
if (!bMDNBlocked)
{
try
{
final IAS2Session aSession = m_aReceiverModule.getSession ();
final IMessageMDN aMdn = AS2Helper.createMDN (aSession, aMsg, aDisposition, sText);
if (aMsg.isRequestingAsynchMDN ())
{
// if asyncMDN requested, close connection and initiate separate MDN
// send
final InternetHeaders aHeaders = new InternetHeaders ();
aHeaders.setHeader (CAS2Header.HEADER_CONTENT_LENGTH, Integer.toString (0));
// Empty data
final NonBlockingByteArrayOutputStream aData = new NonBlockingByteArrayOutputStream ();
aResponseHandler.sendHttpResponse (HttpURLConnection.HTTP_OK, aHeaders, aData);
s_aLogger.info ("Setup to send asynch MDN [" +
aDisposition.getAsString () +
"] " +
sClientInfo +
aMsg.getLoggingText ());
// trigger explicit sending
aSession.getMessageProcessor ().handle (IProcessorSenderModule.DO_SENDMDN, aMsg, null);
}
else
{
// otherwise, send sync MDN back on same connection
s_aLogger.info ("Sending back sync MDN [" +
aDisposition.getAsString () +
"] " +
sClientInfo +
aMsg.getLoggingText ());
// Get data and therefore content length for sync MDN
final NonBlockingByteArrayOutputStream aData = new NonBlockingByteArrayOutputStream ();
final MimeBodyPart aPart = aMdn.getData ();
StreamHelper.copyInputStreamToOutputStream (aPart.getInputStream (), aData);
aMdn.setHeader (CAS2Header.HEADER_CONTENT_LENGTH, Integer.toString (aData.getSize ()));
// start HTTP response
aResponseHandler.sendHttpResponse (HttpURLConnection.HTTP_OK, aMdn.getHeaders (), aData);
// Save sent MDN for later examination
try
{
aSession.getMessageProcessor ().handle (IProcessorStorageModule.DO_STOREMDN, aMsg, null);
}
catch (final ComponentNotFoundException ex)
{
// No message processor found
}
catch (final NoModuleException ex)
{
// No module found in message processor
}
s_aLogger.info ("sent MDN [" + aDisposition.getAsString () + "] " + sClientInfo + aMsg.getLoggingText ());
}
}
catch (final Exception ex)
{
final OpenAS2Exception we = WrappedOpenAS2Exception.wrap (ex);
we.addSource (OpenAS2Exception.SOURCE_MESSAGE, aMsg);
we.terminate ();
}
}
}
/**
* This method can be used to handle an incoming HTTP message AFTER the
* headers where extracted.
*
* @param sClientInfo
* Client connection info
* @param aMsgData
* The message body
* @param aMsg
* The AS2 message that will be filled by this method
* @param aResponseHandler
* The response handler which handles HTTP error messages as well as
* synchronous MDN.
*/
public void handleIncomingMessage (@Nonnull final String sClientInfo,
@Nonnull final byte [] aMsgData,
@Nonnull final AS2Message aMsg,
@Nonnull final IAS2HttpResponseHandler aResponseHandler)
{
// TODO store HTTP request, headers, and data to file in Received folder
// -> use message-id for filename?
try
{
final IAS2Session aSession = m_aReceiverModule.getSession ();
try
{
// Put received data in a MIME body part
final ContentType aReceivedContentType = new ContentType (aMsg.getHeader (CAS2Header.HEADER_CONTENT_TYPE));
final String sReceivedContentType = aReceivedContentType.toString ();
final MimeBodyPart aReceivedPart = new MimeBodyPart ();
aReceivedPart.setDataHandler (new ByteArrayDataSource (aMsgData,
sReceivedContentType,
null).getAsDataHandler ());
// Header must be set AFTER the DataHandler!
aReceivedPart.setHeader (CAS2Header.HEADER_CONTENT_TYPE, sReceivedContentType);
aMsg.setData (aReceivedPart);
}
catch (final Exception ex)
{
throw new DispositionException (DispositionType.createError ("unexpected-processing-error"),
AbstractActiveNetModule.DISP_PARSING_MIME_FAILED,
ex);
}
// Extract AS2 ID's from header, find the message's partnership and
// update the message
try
{
final String sAS2From = aMsg.getAS2From ();
aMsg.getPartnership ().setSenderAS2ID (sAS2From);
final String sAS2To = aMsg.getAS2To ();
aMsg.getPartnership ().setReceiverAS2ID (sAS2To);
// Fill all partnership attributes etc.
aSession.getPartnershipFactory ().updatePartnership (aMsg, false);
}
catch (final OpenAS2Exception ex)
{
throw new DispositionException (DispositionType.createError ("authentication-failed"),
AbstractActiveNetModule.DISP_PARTNERSHIP_NOT_FOUND,
ex);
}
// Per RFC5402 compression is always before encryption but can be before
// or after signing of message but only in one place
final ICryptoHelper aCryptoHelper = AS2Helper.getCryptoHelper ();
boolean bIsDecompressed = false;
// Decrypt and verify signature of the data, and attach data to the
// message
decrypt (aMsg);
if (aCryptoHelper.isCompressed (aMsg.getContentType ()))
{
if (s_aLogger.isTraceEnabled ())
s_aLogger.trace ("Decompressing received message before checking signature...");
decompress (aMsg);
bIsDecompressed = true;
}
verify (aMsg);
if (aCryptoHelper.isCompressed (aMsg.getContentType ()))
{
// Per RFC5402 compression is always before encryption but can be before
// or after signing of message but only in one place
if (bIsDecompressed)
{
throw new DispositionException (DispositionType.createError ("decompression-failed"),
AbstractActiveNetModule.DISP_DECOMPRESSION_ERROR,
new Exception ("Message has already been decompressed. Per RFC5402 it cannot occur twice."));
}
if (s_aLogger.isTraceEnabled ())
if (aMsg.containsAttribute (AS2Message.ATTRIBUTE_RECEIVED_SIGNED))
s_aLogger.trace ("Decompressing received message after verifying signature...");
else
s_aLogger.trace ("Decompressing received message after decryption...");
decompress (aMsg);
bIsDecompressed = true;
}
if (s_aLogger.isTraceEnabled ())
try
{
s_aLogger.trace ("SMIME Decrypted Content-Disposition: " +
aMsg.getContentDisposition () +
"\n Content-Type received: " +
aMsg.getContentType () +
"\n HEADERS after decryption: " +
aMsg.getData ().getAllHeaders () +
"\n Content-Disposition in MSG detData() MIMEPART after decryption: " +
aMsg.getData ().getContentType ());
}
catch (final MessagingException ex)
{
s_aLogger.error ("Failed to trace message: " + aMsg, ex);
}
// Validate the received message before storing
try
{
aSession.getMessageProcessor ().handle (IProcessorStorageModule.DO_VALIDATE_BEFORE_STORE, aMsg, null);
}
catch (final NoModuleException ex)
{
// No module installed - ignore
}
catch (final OpenAS2Exception ex)
{
throw new DispositionException (DispositionType.createError ("unexpected-processing-error"),
AbstractActiveNetModule.DISP_VALIDATION_FAILED +
"\n" +
MessageParameters.getEscapedString (StackTraceHelper.getStackAsString (ex)),
ex);
}
// Store the received message
try
{
aSession.getMessageProcessor ().handle (IProcessorStorageModule.DO_STORE, aMsg, null);
}
catch (final NoModuleException ex)
{
// No module installed - ignore
}
catch (final OpenAS2Exception ex)
{
throw new DispositionException (DispositionType.createError ("unexpected-processing-error"),
AbstractActiveNetModule.DISP_STORAGE_FAILED +
"\n" +
MessageParameters.getEscapedString (ex.getMessage ()),
ex);
}
// Validate the received message after storing
try
{
aSession.getMessageProcessor ().handle (IProcessorStorageModule.DO_VALIDATE_AFTER_STORE, aMsg, null);
}
catch (final NoModuleException ex)
{
// No module installed - ignore
}
catch (final OpenAS2Exception ex)
{
throw new DispositionException (DispositionType.createError ("unexpected-processing-error"),
AbstractActiveNetModule.DISP_VALIDATION_FAILED +
"\n" +
MessageParameters.getEscapedString (StackTraceHelper.getStackAsString (ex)),
ex);
}
try
{
if (aMsg.isRequestingMDN ())
{
// Transmit a success MDN if requested
sendSyncMDN (sClientInfo,
aResponseHandler,
aMsg,
DispositionType.createSuccess (),
AbstractActiveNetModule.DISP_SUCCESS);
}
else
{
// Just send a HTTP OK
HTTPHelper.sendSimpleHTTPResponse (aResponseHandler, HttpURLConnection.HTTP_OK);
s_aLogger.info ("sent HTTP OK " + sClientInfo + aMsg.getLoggingText ());
}
}
catch (final Exception ex)
{
throw new WrappedOpenAS2Exception ("Error creating and returning MDN, message was stilled processed", ex);
}
}
catch (final DispositionException ex)
{
sendSyncMDN (sClientInfo, aResponseHandler, aMsg, ex.getDisposition (), ex.getText ());
m_aReceiverModule.handleError (aMsg, ex);
}
catch (final OpenAS2Exception ex)
{
m_aReceiverModule.handleError (aMsg, ex);
}
}
public void handle (@Nullable final AbstractActiveNetModule aOwner, @Nonnull final Socket aSocket)
{
final String sClientInfo = getClientInfo (aSocket);
s_aLogger.info ("Incoming connection " + sClientInfo);
final AS2Message aMsg = createMessage (aSocket);
final IAS2HttpResponseHandler aResponseHandler = new AS2HttpResponseHandlerSocket (aSocket);
// Time the transmission
final StopWatch aSW = StopWatch.createdStarted ();
byte [] aMsgData = null;
try
{
// Read in the message request, headers, and data
aMsgData = readAndDecodeHttpRequest (new AS2InputStreamProviderSocket (aSocket), aResponseHandler, aMsg);
}
catch (final Exception ex)
{
final NetException ne = new NetException (aSocket.getInetAddress (), aSocket.getPort (), ex);
ne.terminate ();
}
aSW.stop ();
if (aMsgData != null)
{
s_aLogger.info ("received " +
IOHelper.getTransferRate (aMsgData.length, aSW) +
" from " +
sClientInfo +
aMsg.getLoggingText ());
handleIncomingMessage (sClientInfo, aMsgData, aMsg, aResponseHandler);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy