Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -697,6 +697,7 @@ public MimeBodyPart verify (@NonNull final MimeBodyPart aPart,
final boolean bUseCertificateInBodyPart,
final boolean bForceVerifySigned,
@Nullable final Consumer <X509Certificate> aEffectiveCertificateConsumer,
@Nullable final Consumer <MimeBodyPart> aMICSourceConsumer,
@NonNull final AS2ResourceHelper aResHelper) throws GeneralSecurityException,
IOException,
MessagingException,
Expand Down Expand Up @@ -754,6 +755,13 @@ public MimeBodyPart verify (@NonNull final MimeBodyPart aPart,
throw new SignatureException ("Verification failed for SignerInfo " + aSignerInfo);
}

return aSignedParser.getContent ();
final MimeBodyPart aSignedContent = aSignedParser.getContent ();

// Invoke callback with the signed content for MIC calculation
// This mirrors the sender's callback pattern where MIC is calculated on pre-signature content
if (aMICSourceConsumer != null)
aMICSourceConsumer.accept (aSignedContent);

return aSignedContent;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -224,5 +224,6 @@ MimeBodyPart verify (@NonNull MimeBodyPart aPart,
boolean bUseCertificateInBodyPart,
boolean bForceVerifySigned,
@Nullable Consumer <X509Certificate> aEffectiveCertificateConsumer,
@Nullable Consumer <MimeBodyPart> aMICSourceConsumer,
@NonNull AS2ResourceHelper aResHelper) throws Exception;
}
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ public abstract class AbstractMessage extends AbstractBaseMessage implements IMe
private static final Logger LOGGER = LoggerFactory.getLogger (AbstractMessage.class);

private MimeBodyPart m_aData;
private MimeBodyPart m_aMICSource;
private IMessageMDN m_aMDN;
private TempSharedFileInputStream m_aTempSharedFileInputStream;

Expand Down Expand Up @@ -152,6 +153,17 @@ public final void setData (@Nullable final MimeBodyPart aData)
}
}

@Nullable
public final MimeBodyPart getMICSource ()
{
return m_aMICSource;
}

public final void setMICSource (@Nullable final MimeBodyPart aMICSource)
{
m_aMICSource = aMICSource;
}

@Nullable
public final IMessageMDN getMDN ()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,11 @@ default void setSubject (@Nullable final String sSubject)

void setData (@NonNull MimeBodyPart aData);

@Nullable
MimeBodyPart getMICSource ();

void setMICSource (@Nullable MimeBodyPart aMICSource);

@Nullable
IMessageMDN getMDN ();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
package com.helger.phase2.processor.receiver.net;

import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
Expand Down Expand Up @@ -302,18 +303,24 @@ protected void verify (@NonNull final IMessage aMsg, @NonNull final AS2ResourceH
}

final Wrapper <X509Certificate> aCertHolder = new Wrapper <> ();
final Wrapper <MimeBodyPart> aMICSourceHolder = new Wrapper <> ();
final MimeBodyPart aVerifiedData = aCryptoHelper.verify (aMsg.getData (),
aSenderCert,
bUseCertificateInBodyPart,
bForceVerify,
aCertHolder::set,
aMICSourceHolder::set,
aResHelper);
final Consumer <X509Certificate> aExternalConsumer = getVerificationCertificateConsumer ();
if (aExternalConsumer != null)
aExternalConsumer.accept (aCertHolder.get ());

aMsg.setData (aVerifiedData);

// Store the MIC source for later calculation (mirrors sender's callback pattern)
if (aMICSourceHolder.isSet ())
aMsg.setMICSource (aMICSourceHolder.get ());

// Remember that message was signed and verified
aMsg.attrs ().putIn (AS2Message.ATTRIBUTE_RECEIVED_SIGNED, true);

Expand Down Expand Up @@ -531,11 +538,27 @@ public void handleIncomingMessage (@NonNull final String sClientInfo,
// Put received data in a MIME body part
final String sReceivedContentType = AS2HttpHelper.getCleanContentType (aMsg.getHeader (CHttpHeader.CONTENT_TYPE));

// Read raw bytes from DataSource to preserve original content for MIC calculation
final byte [] aRawBytes;
try (final InputStream aIS = aMsgData.getInputStream ())
{
aRawBytes = StreamHelper.getAllBytes (aIS);
}

// Create MimeBodyPart with raw bytes using ByteArrayDataSource
// This ensures MIC calculation uses exact bytes as received, without JavaMail modifications
final ByteArrayDataSource aByteArrayDS = new ByteArrayDataSource (aRawBytes, sReceivedContentType, null);
final MimeBodyPart aReceivedPart = new MimeBodyPart ();
aReceivedPart.setDataHandler (new DataHandler (aMsgData));
aReceivedPart.setDataHandler (new DataHandler (aByteArrayDS));

// Header must be set AFTER the DataHandler!
aReceivedPart.setHeader (CHttpHeader.CONTENT_TYPE, sReceivedContentType);

// Copy Content-Disposition from HTTP headers if present (important for MIC calculation on unsigned messages)
final String sContentDisposition = aMsg.getHeader (CHttpHeader.CONTENT_DISPOSITION);
if (sContentDisposition != null)
aReceivedPart.setHeader (CHttpHeader.CONTENT_DISPOSITION, sContentDisposition);

aMsg.setData (aReceivedPart);
}
catch (final Exception ex)
Expand Down Expand Up @@ -565,17 +588,6 @@ public void handleIncomingMessage (@NonNull final String sClientInfo,
() -> AbstractActiveNetModule.DISP_PARTNERSHIP_NOT_FOUND);
}

// Calculate MIC before decrypt and decompress (see #140)
try
{
aIncomingMIC = AS2Helper.createMICOnReception (aMsg);
}
catch (final Exception ex)
{
// Ignore error
throw WrappedAS2Exception.wrap (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 ();
Expand All @@ -596,6 +608,26 @@ public void handleIncomingMessage (@NonNull final String sClientInfo,
// Verify may fail, if our certificate is expired
verify (aMsg, aResHelper);

// For unsigned messages, set MIC source to current data (after decrypt/decompress)
// For signed messages, this was already set by the verify() callback
if (aMsg.getMICSource () == null)
{
aMsg.setMICSource (aMsg.getData ());
}

// Calculate MIC AFTER decryption and signature verification (RFC 4130)
// The MIC must be calculated on the same data that the sender calculated it on,
// which is the decrypted signed content, not the encrypted envelope
try
{
aIncomingMIC = AS2Helper.createMICOnReception (aMsg);
}
catch (final Exception ex)
{
// Ignore error
throw WrappedAS2Exception.wrap (ex);
}

if (aCryptoHelper.isCompressed (aMsg.getContentType ()))
{
// Per RFC5402 compression is always before encryption but can be
Expand Down
19 changes: 18 additions & 1 deletion phase2-lib/src/main/java/com/helger/phase2/util/AS2Helper.java
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,23 @@ public static MIC createMICOnReception (@NonNull final AS2Message aMsg) throws E
aPartnership.getEncryptAlgorithm () != null ||
aPartnership.getCompressionType () != null;

return getCryptoHelper ().calculateMIC (aMsg.getData (), eSigningAlgorithm, bIncludeHeadersInMIC);
// Use the MIC source captured during signature verification (via callback)
// This mirrors the sender's callback pattern where MIC is calculated on pre-signature content
MimeBodyPart aPartToHash = aMsg.getMICSource ();
if (aPartToHash == null)
{
// Fallback for unsigned messages - use the message data directly
aPartToHash = aMsg.getData ();
LOGGER.info ("createMICOnReception: No MIC source captured (unsigned message), using message data directly");
}
else
{
LOGGER.info ("createMICOnReception: Using captured MIC source from signature verification");
}

LOGGER.info ("createMICOnReception: signingAlgorithm=" + aPartnership.getSigningAlgorithm () + ", contentType=" + aPartToHash.getContentType ());

return getCryptoHelper ().calculateMIC (aPartToHash, eSigningAlgorithm, bIncludeHeadersInMIC);
}

/**
Expand Down Expand Up @@ -444,6 +460,7 @@ public static void parseMDN (@NonNull final IMessage aMsg,
bUseCertificateInBodyPart,
bForceVerify,
aCertHolder::set,
null,
aResHelper);
if (aEffectiveCertificateConsumer != null)
aEffectiveCertificateConsumer.accept (aCertHolder.get ());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ public void testSignWithAllAlgorithms () throws Exception

// Verify as well
LOGGER.info (" Now verifying result of signing algo " + eAlgo);
aCryptoHelper.verify (aSigned, (X509Certificate) PKE.getCertificate (), bIncludeCert, true, null, aResHelper);
aCryptoHelper.verify (aSigned, (X509Certificate) PKE.getCertificate (), bIncludeCert, true, null, null, aResHelper);
}
}
}
Expand Down