Skip to content

Commit 5213637

Browse files
committed
Fiddling around with MIC calculation
1 parent c736c78 commit 5213637

File tree

7 files changed

+303
-80
lines changed

7 files changed

+303
-80
lines changed

phase2-lib/src/main/java/com/helger/phase2/crypto/BCCryptoHelper.java

Lines changed: 47 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@
4747
import java.security.cert.X509Certificate;
4848
import java.util.Collection;
4949
import java.util.Collections;
50-
import java.util.Enumeration;
5150
import java.util.LinkedHashMap;
5251
import java.util.Locale;
5352
import java.util.Map;
@@ -108,7 +107,6 @@
108107
import com.helger.http.CHttpHeader;
109108
import com.helger.io.file.FileHelper;
110109
import com.helger.mail.cte.EContentTransferEncoding;
111-
import com.helger.phase2.exception.AS2Exception;
112110
import com.helger.phase2.util.AS2HttpHelper;
113111
import com.helger.phase2.util.AS2IOHelper;
114112
import com.helger.phase2.util.AS2ResourceHelper;
@@ -260,13 +258,23 @@ public KeyStore loadKeyStore (@Nonnull final IKeyStoreType aKeyStoreType,
260258
return aKeyStore;
261259
}
262260

263-
public boolean isEncrypted (@Nonnull final MimeBodyPart aPart) throws MessagingException
261+
public boolean isEncrypted (@Nonnull final MimeBodyPart aPart)
264262
{
265263
ValueEnforcer.notNull (aPart, "Part");
266264

265+
String sContentType;
266+
try
267+
{
268+
sContentType = aPart.getContentType ();
269+
}
270+
catch (final MessagingException ex)
271+
{
272+
sContentType = null;
273+
}
274+
267275
// Content-Type is sthg like this if encrypted:
268276
// application/pkcs7-mime; name=smime.p7m; smime-type=enveloped-data
269-
final ContentType aContentType = AS2HttpHelper.parseContentType (aPart.getContentType ());
277+
final ContentType aContentType = AS2HttpHelper.parseContentType (sContentType);
270278
if (aContentType == null)
271279
return false;
272280

@@ -278,19 +286,28 @@ public boolean isEncrypted (@Nonnull final MimeBodyPart aPart) throws MessagingE
278286
return sSmimeType != null && sSmimeType.equalsIgnoreCase ("enveloped-data");
279287
}
280288

281-
public boolean isSigned (@Nonnull final MimeBodyPart aPart) throws MessagingException
289+
public boolean isSigned (@Nonnull final MimeBodyPart aPart)
282290
{
283291
ValueEnforcer.notNull (aPart, "Part");
284292

285-
final ContentType aContentType = AS2HttpHelper.parseContentType (aPart.getContentType ());
293+
String sContentType;
294+
try
295+
{
296+
sContentType = aPart.getContentType ();
297+
}
298+
catch (final MessagingException ex)
299+
{
300+
sContentType = null;
301+
}
302+
final ContentType aContentType = AS2HttpHelper.parseContentType (sContentType);
286303
if (aContentType == null)
287304
return false;
288305

289306
final String sBaseType = aContentType.getBaseType ();
290307
return sBaseType.equalsIgnoreCase ("multipart/signed");
291308
}
292309

293-
public boolean isCompressed (@Nonnull final String sContentType) throws AS2Exception
310+
public boolean isCompressed (@Nonnull final String sContentType)
294311
{
295312
ValueEnforcer.notNull (sContentType, "ContentType");
296313

@@ -332,23 +349,33 @@ public MIC calculateMIC (@Nonnull final MimeBodyPart aPart,
332349
aMessageDigest = new LoggingMessageDigest (aMessageDigest);
333350
}
334351

352+
final int nHeadersIncluded;
353+
final StringBuilder aHeadersIncluded = new StringBuilder ();
335354
if (bIncludeHeaders)
336355
{
337356
// Start hashing the header
338-
final Enumeration <String> aHeaderLines = aPart.getAllHeaderLines ();
339-
while (aHeaderLines.hasMoreElements ())
357+
final ICommonsList <String> aHeaderLines = new CommonsArrayList <> (aPart.getAllHeaderLines ());
358+
for (final String sHeaderLine : aHeaderLines)
340359
{
341-
final String sHeaderLine = aHeaderLines.nextElement ();
342-
343360
aMessageDigest.update (AS2IOHelper.getAllAsciiBytes (sHeaderLine));
344361
aMessageDigest.update (EOL_BYTES);
345362

346363
if (LOGGER.isDebugEnabled ())
347364
LOGGER.debug ("Using header line '" + sHeaderLine + "' for MIC calculation");
365+
366+
// For debug logging only
367+
if (aHeadersIncluded.length () > 0)
368+
aHeadersIncluded.append (':');
369+
aHeadersIncluded.append (sHeaderLine.substring (0, sHeaderLine.indexOf (':')));
348370
}
349371

350372
// The CRLF separator between header and content
351373
aMessageDigest.update (EOL_BYTES);
374+
nHeadersIncluded = aHeaderLines.size ();
375+
}
376+
else
377+
{
378+
nHeadersIncluded = -1;
352379
}
353380

354381
final String sMICEncoding = aPart.getEncoding ();
@@ -372,8 +399,14 @@ public MIC calculateMIC (@Nonnull final MimeBodyPart aPart,
372399
// Perform Base64 encoding and append algorithm ID
373400
final MIC ret = new MIC (aMIC, eDigestAlgorithm);
374401

375-
if (LOGGER.isDebugEnabled ())
376-
LOGGER.debug (" Calculated MIC = " + ret.getAsAS2String ());
402+
LOGGER.info (" Calculated MIC[" +
403+
eDigestAlgorithm +
404+
"][" +
405+
nHeadersIncluded +
406+
" headers; " +
407+
aHeadersIncluded.toString () +
408+
"] = " +
409+
ret.getAsAS2String ());
377410

378411
return ret;
379412
}
@@ -715,11 +748,9 @@ public MimeBodyPart verify (@Nonnull final MimeBodyPart aPart,
715748
// Get only once and check
716749
// Throws "ParseException" if it is not a MIME message
717750
final Object aContent = aPart.getContent ();
718-
if (!(aContent instanceof MimeMultipart))
751+
if (!(aContent instanceof final MimeMultipart aMainPart))
719752
throw new IllegalStateException ("Expected Part content to be MimeMultipart but it isn't. It is " +
720753
ClassHelper.getClassName (aContent));
721-
final MimeMultipart aMainPart = (MimeMultipart) aContent;
722-
723754
// SMIMESignedParser uses "7bit" as the default - AS2 wants "binary"
724755
final SMIMESignedParser aSignedParser = new SMIMESignedParser (new JcaDigestCalculatorProviderBuilder ().setProvider (m_sSecurityProviderName)
725756
.build (),

phase2-lib/src/main/java/com/helger/phase2/crypto/ICryptoHelper.java

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@
4141

4242
import com.helger.annotation.WillNotClose;
4343
import com.helger.mail.cte.EContentTransferEncoding;
44-
import com.helger.phase2.exception.AS2Exception;
4544
import com.helger.phase2.util.AS2ResourceHelper;
4645
import com.helger.security.keystore.IKeyStoreType;
4746

@@ -92,10 +91,8 @@ KeyStore loadKeyStore (@Nonnull IKeyStoreType aKeyStoreType,
9291
* @param aPart
9392
* The part to be checked.
9493
* @return <code>true</code> if it is encrypted, <code>false</code> otherwise.
95-
* @throws Exception
96-
* In case something goes wrong.
9794
*/
98-
boolean isEncrypted (@Nonnull MimeBodyPart aPart) throws Exception;
95+
boolean isEncrypted (@Nonnull MimeBodyPart aPart);
9996

10097
/**
10198
* Check if the passed MIME body part is signed. The default implementation checks if the base
@@ -104,10 +101,8 @@ KeyStore loadKeyStore (@Nonnull IKeyStoreType aKeyStoreType,
104101
* @param aPart
105102
* The part to be checked.
106103
* @return <code>true</code> if it is signed, <code>false</code> otherwise.
107-
* @throws Exception
108-
* In case something goes wrong.
109104
*/
110-
boolean isSigned (@Nonnull MimeBodyPart aPart) throws Exception;
105+
boolean isSigned (@Nonnull MimeBodyPart aPart);
111106

112107
/**
113108
* Check if the passed content type indicates compression. The default implementation checks if
@@ -116,10 +111,8 @@ KeyStore loadKeyStore (@Nonnull IKeyStoreType aKeyStoreType,
116111
* @param sContentType
117112
* The content type to be checked. May not be <code>null</code>.
118113
* @return <code>true</code> if it is compressed, <code>false</code> otherwise.
119-
* @throws AS2Exception
120-
* In case something goes wrong.
121114
*/
122-
boolean isCompressed (@Nonnull String sContentType) throws AS2Exception;
115+
boolean isCompressed (@Nonnull String sContentType);
123116

124117
/**
125118
* Calculate the MIC

phase2-lib/src/main/java/com/helger/phase2/processor/receiver/net/AS2ReceiverHandler.java

Lines changed: 57 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
import java.security.PrivateKey;
3838
import java.security.cert.X509Certificate;
3939
import java.util.Enumeration;
40+
import java.util.Locale;
4041
import java.util.function.Consumer;
4142

4243
import org.bouncycastle.cms.CMSException;
@@ -565,26 +566,15 @@ public void handleIncomingMessage (@Nonnull final String sClientInfo,
565566
() -> AbstractActiveNetModule.DISP_PARTNERSHIP_NOT_FOUND);
566567
}
567568

568-
// Calculate MIC before decrypt and decompress (see #140)
569-
try
570-
{
571-
aIncomingMIC = AS2Helper.createMICOnReception (aMsg);
572-
}
573-
catch (final Exception ex)
574-
{
575-
// Ignore error
576-
throw WrappedAS2Exception.wrap (ex);
577-
}
569+
// Decrypt and verify signature of the data, and attach data to the
570+
// message
571+
decrypt (aMsg, aResHelper);
578572

579573
// Per RFC5402 compression is always before encryption but can be before
580574
// or after signing of message but only in one place
581575
final ICryptoHelper aCryptoHelper = AS2Helper.getCryptoHelper ();
582-
boolean bIsDecompressed = false;
583-
584-
// Decrypt and verify signature of the data, and attach data to the
585-
// message
586-
decrypt (aMsg, aResHelper);
587576

577+
boolean bIsDecompressed = false;
588578
if (aCryptoHelper.isCompressed (aMsg.getContentType ()))
589579
{
590580
if (LOGGER.isTraceEnabled ())
@@ -596,6 +586,46 @@ public void handleIncomingMessage (@Nonnull final String sClientInfo,
596586
// Verify may fail, if our certificate is expired
597587
verify (aMsg, aResHelper);
598588

589+
// Calculate MIC state signed by the sender (might have been compressed
590+
// or not) (see #140)
591+
// Do this only after the partnership was determined
592+
// Do this after decryption
593+
final boolean bIsSigned = aCryptoHelper.isSigned (aMsg.getData ());
594+
if (bIsSigned)
595+
try
596+
{
597+
598+
// Copy all Content-related headers to the MIME part. Maintain the order of the source
599+
// message. This is relevant for MIC calculation
600+
if (true)
601+
aMsg.headers ().forEachSingleHeader ( (k, v) -> {
602+
if (k.toLowerCase (Locale.ROOT).startsWith ("content-") &&
603+
!CHttpHeader.CONTENT_TYPE.equalsIgnoreCase (k) &&
604+
!CHttpHeader.CONTENT_LENGTH.equalsIgnoreCase (k))
605+
try
606+
{
607+
// Don't overwrite
608+
if (aMsg.getData ().getHeader (k) == null)
609+
{
610+
LOGGER.info (" Setting MIME part header '" + k + "' to '" + v + "'");
611+
aMsg.getData ().setHeader (k, v);
612+
}
613+
}
614+
catch (final MessagingException ex)
615+
{
616+
LOGGER.error ("Error storing header '" + k + "' with value '" + v + "' in MimeBodyPart");
617+
}
618+
}, false);
619+
620+
// From signed payload
621+
aIncomingMIC = AS2Helper.createMICOnReception (aMsg);
622+
}
623+
catch (final Exception ex)
624+
{
625+
// Ignore error
626+
throw WrappedAS2Exception.wrap (ex);
627+
}
628+
599629
if (aCryptoHelper.isCompressed (aMsg.getContentType ()))
600630
{
601631
// Per RFC5402 compression is always before encryption but can be
@@ -616,6 +646,18 @@ public void handleIncomingMessage (@Nonnull final String sClientInfo,
616646
bIsDecompressed = true;
617647
}
618648

649+
if (!bIsSigned)
650+
try
651+
{
652+
// From uncompressed payload
653+
aIncomingMIC = AS2Helper.createMICOnReception (aMsg);
654+
}
655+
catch (final Exception ex)
656+
{
657+
// Ignore error
658+
throw WrappedAS2Exception.wrap (ex);
659+
}
660+
619661
if (LOGGER.isTraceEnabled ())
620662
try
621663
{

0 commit comments

Comments
 (0)