/* * Copyright(c) 2005 Center for E-Commerce Infrastructure Development, The * University of Hong Kong (HKU). All Rights Reserved. * * This software is licensed under the GNU GENERAL PUBLIC LICENSE Version 2.0 [1] * * [1] http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt */ package hk.hku.cecid.edi.as2.module.test; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.InputStream; import java.security.MessageDigest; import java.util.Arrays; import java.util.Iterator; import javax.activation.CommandMap; import javax.activation.MailcapCommandMap; import javax.mail.internet.MimeBodyPart; import javax.mail.internet.MimeMultipart; import org.junit.Ignore; import org.junit.Test; import junit.framework.Assert; import org.bouncycastle.cms.RecipientId; import org.bouncycastle.cms.RecipientInformation; import org.bouncycastle.cms.RecipientInformationStore; import org.bouncycastle.cms.SignerInformation; import org.bouncycastle.cms.SignerInformationStore; import org.bouncycastle.mail.smime.SMIMECompressed; import org.bouncycastle.mail.smime.SMIMEEnveloped; import org.bouncycastle.mail.smime.SMIMESigned; import org.bouncycastle.mail.smime.SMIMEUtil; import org.bouncycastle.util.encoders.Base64; import hk.hku.cecid.edi.as2.dao.MessageDAO; import hk.hku.cecid.edi.as2.dao.MessageDVO; import hk.hku.cecid.edi.as2.dao.PartnershipDAO; import hk.hku.cecid.edi.as2.dao.PartnershipDVO; import hk.hku.cecid.edi.as2.module.OutgoingMessageProcessor; import hk.hku.cecid.edi.as2.pkg.AS2Message; import hk.hku.cecid.piazza.commons.activation.InputStreamDataSource; import hk.hku.cecid.piazza.commons.dao.DAOException; import hk.hku.cecid.piazza.commons.io.IOHandler; import hk.hku.cecid.piazza.commons.security.KeyStoreManager; import hk.hku.cecid.piazza.commons.test.SystemComponentTest; /** * Unit Test OutgoingMessageProcessor * * @author Jumbo Cheung * */ @Ignore public class OutgoingMessageProcessorTest extends SystemComponentTest<OutgoingMessageProcessor>{ PartnershipDAO partnershipDAO; PartnershipDVO partnershipDVO; static final String CREATE_TABLE_SQL = "create.sql"; static final String DROP_TABLE_SQL = "drop.sql"; private static String MOCK_AS2_MSG = "mock.as2"; @Override public String getSystemComponentId() { return "outgoing-message-processor"; } @Override public void setUp() throws Exception { commitSQL(MessageDAO.class, CREATE_TABLE_SQL); //Prepare the Partnership DVO partnershipDAO = (PartnershipDAO) TARGET.getDAOFactory().createDAO(PartnershipDAO.class); partnershipDVO = (PartnershipDVO) partnershipDAO.createDVO(); partnershipDVO.setPartnershipId("OutgoingMessageProcessorTest_P1"); partnershipDVO.setIsDisabled(false); partnershipDVO.setAs2From("as2Form"); partnershipDVO.setAs2To("as2To"); partnershipDVO.setSubject("OutgoingMessageProcessor Unit Test"); partnershipDVO.setIsSyncReply(false); partnershipDVO.setReceiptAddress("http://127.0.0.1:8080/corvus/httpd/as2/inbound"); partnershipDVO.setRecipientAddress("http://127.0.0.1:8080/corvus/httpd/as2/inbound"); partnershipDVO.setIsReceiptRequired(false); partnershipDVO.setIsReceiptSignRequired(true); partnershipDVO.setIsInboundEncryptRequired(false); partnershipDVO.setIsInboundSignRequired(false); partnershipDVO.setIsOutboundCompressRequired(false); partnershipDVO.setIsOutboundEncryptRequired(false); partnershipDVO.setIsOutboundSignRequired(false); partnershipDVO.setSignAlgorithm(PartnershipDVO.ALG_SIGN_SHA1); partnershipDVO.setEncryptAlgorithm(PartnershipDVO.ALG_ENCRYPT_3DES); partnershipDVO.setMicAlgorithm(PartnershipDVO.ALG_MIC_SHA1); partnershipDVO.setVerifyCert(IOHandler.readBytes(FIXTURE_LOADER.getResourceAsStream("security/corvus.cer"))); partnershipDVO.setEncryptCert(IOHandler.readBytes(FIXTURE_LOADER.getResourceAsStream("security/corvus.cer"))); partnershipDAO.create(partnershipDVO); //Seting Mail Cap MailcapCommandMap mailcaps = new MailcapCommandMap(); mailcaps.addMailcap("application/pkcs7-signature;; x-java-content-handler=org.bouncycastle.mail.smime.handlers.pkcs7_signature"); mailcaps.addMailcap("application/pkcs7-mime;; x-java-content-handler=org.bouncycastle.mail.smime.handlers.pkcs7_mime"); mailcaps.addMailcap("application/x-pkcs7-signature;; x-java-content-handler=org.bouncycastle.mail.smime.handlers.x_pkcs7_signature"); mailcaps.addMailcap("application/x-pkcs7-mime;; x-java-content-handler=org.bouncycastle.mail.smime.handlers.x_pkcs7_mime"); mailcaps.addMailcap("multipart/signed;; x-java-content-handler=org.bouncycastle.mail.smime.handlers.multipart_signed"); mailcaps.addMailcap("text/xml;; x-java-content-handler=com.sun.mail.handlers.text_xml"); mailcaps.addMailcap("application/deflate;; x-java-content-handler=hk.hku.cecid.piazza.commons.activation.ByteStreamDataContentHandler"); mailcaps.addMailcap("message/disposition-notification;; x-java-content-handler=hk.hku.cecid.piazza.commons.activation.ByteStreamDataContentHandler"); mailcaps.addMailcap("application/EDI-X12;; x-java-content-handler=hk.hku.cecid.piazza.commons.activation.ByteStreamDataContentHandler"); mailcaps.addMailcap("application/EDIFACT;; x-java-content-handler=hk.hku.cecid.piazza.commons.activation.ByteStreamDataContentHandler"); mailcaps.addMailcap("application/edi-consent;; x-java-content-handler=hk.hku.cecid.piazza.commons.activation.ByteStreamDataContentHandler"); mailcaps.addMailcap("application/XML;; x-java-content-handler=hk.hku.cecid.piazza.commons.activation.ByteStreamDataContentHandler"); mailcaps.addMailcap("application/octet-stream;; x-java-content-handler=hk.hku.cecid.piazza.commons.activation.ByteStreamDataContentHandler"); CommandMap.setDefaultCommandMap(mailcaps); } @Override public void tearDown() throws Exception { commitSQL(MessageDAO.class, DROP_TABLE_SQL); } @Test public void testPlainMessageMIC() throws Exception{ //Prepare Data InputStream ins = FIXTURE_LOADER.getResourceAsStream(MOCK_AS2_MSG); ByteArrayInputStream bIns = new ByteArrayInputStream(IOHandler.readBytes(ins)); partnershipDVO.setIsReceiptSignRequired(true); partnershipDVO.setIsReceiptRequired(true); String mid = RANDOM.toString(); AS2Message as2Msg = TARGET.storeOutgoingMessage( mid, //MessageID "xml", partnershipDVO, new InputStreamDataSource(bIns, "xml", MOCK_AS2_MSG), null); //Verify MIC value ins = FIXTURE_LOADER.getResourceAsStream(MOCK_AS2_MSG); bIns = new ByteArrayInputStream(IOHandler.readBytes(ins)); byte[] content = new byte[bIns.available()]; bIns.read(content); bIns.close(); String mic = calculateMIC(content); MessageDVO msgDVO = getStoredMessage(mid); Assert.assertEquals( "MIC value is not valid", mic, msgDVO.getMicValue()); } @Test public void testSignedAS2Message() throws Exception{ InputStream ins = FIXTURE_LOADER.getResourceAsStream(MOCK_AS2_MSG); ByteArrayInputStream bIns = new ByteArrayInputStream(IOHandler.readBytes(ins)); partnershipDVO.setIsOutboundSignRequired(true); String mid = RANDOM.toString(); AS2Message as2Msg = TARGET.storeOutgoingMessage( mid, //MessageID "xml", partnershipDVO, new InputStreamDataSource(bIns, "xml", MOCK_AS2_MSG)); //Verify As2Signing Message try{ SMIMESigned signed = new SMIMESigned((MimeMultipart)as2Msg.getBodyPart().getContent()); SignerInformationStore signers = signed.getSignerInfos(); Iterator signerInfos = signers.getSigners().iterator(); while (signerInfos.hasNext()) { SignerInformation signerInfo = (SignerInformation)signerInfos.next(); if (!signerInfo.verify(partnershipDVO.getEffectiveVerifyCertificate(), "BC")) { Assert.fail("Signature Verfifcation Failed"); } } //Assert the filename value MimeBodyPart signedPart = signed.getContent(); String filenameHdr = signedPart.getHeader("Content-Disposition")[0]; Assert.assertEquals("Lost Filename Header Information", MOCK_AS2_MSG, getFileName(filenameHdr)); // Verify MIC Value ByteArrayOutputStream baos = new ByteArrayOutputStream(); signedPart.writeTo(baos); byte[] content = (baos.toByteArray()); String mic = calculateMIC(content); MessageDVO msgDVO = getStoredMessage(mid); Assert.assertEquals("MIC Value is not valid.", mic, msgDVO.getMicValue()); }catch(Exception exp){ Assert.fail("Signature Verfifcation Failed"); } Assert.assertTrue(true); } @Test public void testCompressAS2Message() throws Exception{ InputStream ins = FIXTURE_LOADER.getResourceAsStream(MOCK_AS2_MSG); ByteArrayInputStream bIns = new ByteArrayInputStream(IOHandler.readBytes(ins)); partnershipDVO.setIsOutboundCompressRequired(true); String mid = RANDOM.toString(); AS2Message as2Msg = TARGET.storeOutgoingMessage( mid, //MessageID "xml", partnershipDVO, new InputStreamDataSource(bIns, "xml", MOCK_AS2_MSG)); SMIMECompressed compressed = new SMIMECompressed(as2Msg.getBodyPart()); MimeBodyPart decompressedPart = SMIMEUtil.toMimeBodyPart(compressed.getContent()); ByteArrayOutputStream baos = new ByteArrayOutputStream(); IOHandler.pipe( decompressedPart.getDataHandler().getInputStream(), baos); byte[] decrptedBA = baos.toByteArray(); byte[] original = IOHandler.readBytes(FIXTURE_LOADER.getResourceAsStream(MOCK_AS2_MSG)); Assert.assertTrue(Arrays.equals(decrptedBA, original)); //TODO String filenameHdr = decompressedPart.getHeader("Content-Disposition")[0]; Assert.assertEquals("Filename value lost in BodyPart Header", MOCK_AS2_MSG, getFileName(filenameHdr)); // Verify MIC Value ByteArrayOutputStream contentBAOS = new ByteArrayOutputStream(); decompressedPart.writeTo(contentBAOS); byte[] content = (contentBAOS.toByteArray()); String mic = calculateMIC(content); Assert.assertEquals( "MIC Value is not valid.", mic, getStoredMessage(mid).getMicValue()); } @Test public void testEncrytedAS2Message() throws Exception{ InputStream ins = FIXTURE_LOADER.getResourceAsStream(MOCK_AS2_MSG); ByteArrayInputStream bIns = new ByteArrayInputStream(IOHandler.readBytes(ins)); String mid = RANDOM.toString(); partnershipDVO.setIsOutboundEncryptRequired(true); AS2Message as2Msg = TARGET.storeOutgoingMessage( mid, //MessageID "xml", partnershipDVO, new InputStreamDataSource(bIns, "xml", MOCK_AS2_MSG)); // Decrypt Message SMIMEEnveloped crypted = new SMIMEEnveloped(as2Msg.getBodyPart()); RecipientId recId = new RecipientId(); recId.setSerialNumber(partnershipDVO.getEncryptX509Certificate().getSerialNumber()); recId.setIssuer(partnershipDVO.getEncryptX509Certificate().getIssuerX500Principal().getEncoded()); RecipientInformationStore recipients = crypted.getRecipientInfos(); RecipientInformation recipient = recipients.get(recId); KeyStoreManager keyMan = (KeyStoreManager)TARGET.getSystemModule().getComponent("keystore-manager"); MimeBodyPart decrpted = SMIMEUtil.toMimeBodyPart(recipient.getContent( keyMan.getPrivateKey(), "BC")); ByteArrayOutputStream baos = new ByteArrayOutputStream(); IOHandler.pipe( decrpted.getDataHandler().getInputStream(), baos); byte[] decrptedBA = baos.toByteArray(); byte[] originalBA = IOHandler.readBytes(FIXTURE_LOADER.getResourceAsStream(MOCK_AS2_MSG)); Assert.assertTrue(Arrays.equals(decrptedBA, originalBA)); //Assert the filename String filenameHdr = decrpted.getHeader("Content-Disposition")[0]; Assert.assertEquals("Filename value lost in BodyPartHeader", MOCK_AS2_MSG, getFileName(filenameHdr)); //Verify MIC ByteArrayOutputStream contentStream = new ByteArrayOutputStream(); decrpted.writeTo(contentStream); byte[] content = (contentStream.toByteArray()); String mic = calculateMIC(content); Assert.assertEquals( "MIC Value is not valid.", mic, getStoredMessage(mid).getMicValue()); } @Test public void testSignedEncryptedAS2Message() throws Exception { InputStream ins = FIXTURE_LOADER.getResourceAsStream(MOCK_AS2_MSG); ByteArrayInputStream bIns = new ByteArrayInputStream(IOHandler.readBytes(ins)); // Prepare Data String mid = RANDOM.toString(); partnershipDVO.setIsOutboundEncryptRequired(true); partnershipDVO.setIsOutboundSignRequired(true); //Encrypt message AS2Message as2Msg = TARGET.storeOutgoingMessage( mid, //MessageID "xml", partnershipDVO, new InputStreamDataSource(bIns, "xml", MOCK_AS2_MSG)); // Decrypt Message SMIMEEnveloped crypted = new SMIMEEnveloped(as2Msg.getBodyPart()); RecipientId recId = new RecipientId(); recId.setSerialNumber(partnershipDVO.getEncryptX509Certificate().getSerialNumber()); recId.setIssuer(partnershipDVO.getEncryptX509Certificate().getIssuerX500Principal().getEncoded()); RecipientInformationStore recipients = crypted.getRecipientInfos(); RecipientInformation recipient = recipients.get(recId); KeyStoreManager keyMan = (KeyStoreManager)TARGET.getSystemModule().getComponent("keystore-manager"); MimeBodyPart decrpted = SMIMEUtil.toMimeBodyPart(recipient.getContent( keyMan.getPrivateKey(), "BC")); //Verify Signature try{ SMIMESigned signed = new SMIMESigned((MimeMultipart)decrpted.getContent()); SignerInformationStore signers = signed.getSignerInfos(); Iterator signerInfos = signers.getSigners().iterator(); while (signerInfos.hasNext()) { SignerInformation signerInfo = (SignerInformation)signerInfos.next(); if (!signerInfo.verify(partnershipDVO.getEffectiveVerifyCertificate(), "BC")) { Assert.fail("Signature Verfifcation Failed"); } } //Assert the filename value MimeBodyPart signedPart = signed.getContent(); String filenameHdr = signedPart.getHeader("Content-Disposition")[0]; Assert.assertEquals("Lost Filename Header Information", MOCK_AS2_MSG, getFileName(filenameHdr)); // Verify MIC Value ByteArrayOutputStream baos = new ByteArrayOutputStream(); signedPart.writeTo(baos); byte[] content = (baos.toByteArray()); String mic = calculateMIC(content); MessageDVO msgDVO = getStoredMessage(mid); Assert.assertEquals("MIC Value is not valid.", mic, msgDVO.getMicValue()); }catch(Exception exp){ Assert.fail("Signature Verfifcation Failed"); } Assert.assertTrue(true); } @Test public void testSignedCommpressMessage() throws Exception{ InputStream ins = FIXTURE_LOADER.getResourceAsStream(MOCK_AS2_MSG); ByteArrayInputStream bIns = new ByteArrayInputStream(IOHandler.readBytes(ins)); // Prepare Data String mid = RANDOM.toString(); partnershipDVO.setIsOutboundSignRequired(true); partnershipDVO.setIsOutboundCompressRequired(true); //Process message AS2Message as2Msg = TARGET.storeOutgoingMessage( mid, //MessageID "xml", partnershipDVO, new InputStreamDataSource(bIns, "xml", MOCK_AS2_MSG)); try{ //Verify Message Signature SMIMESigned signed = new SMIMESigned((MimeMultipart)as2Msg.getBodyPart().getContent()); SignerInformationStore signers = signed.getSignerInfos(); Iterator signerInfos = signers.getSigners().iterator(); while (signerInfos.hasNext()) { SignerInformation signerInfo = (SignerInformation)signerInfos.next(); if (!signerInfo.verify(partnershipDVO.getEffectiveVerifyCertificate(), "BC")) { Assert.fail("Signature Verfifcation Failed"); } } // Verify MIC Value MimeBodyPart signedPart = signed.getContent(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); signedPart.writeTo(baos); byte[] content = (baos.toByteArray()); String mic = calculateMIC(content); MessageDVO msgDVO = getStoredMessage(mid); Assert.assertEquals("MIC Value is not valid.", mic, msgDVO.getMicValue()); //Decompress Message SMIMECompressed compressed = new SMIMECompressed(signedPart); MimeBodyPart decompressedPart = SMIMEUtil.toMimeBodyPart(compressed.getContent()); //Assert the filename value String filenameHdr = decompressedPart.getHeader("Content-Disposition")[0]; Assert.assertEquals("Lost Filename Header Information", MOCK_AS2_MSG, getFileName(filenameHdr)); }catch(Exception exp){ Assert.fail("Signature Verfifcation Failed"); } } private MessageDVO getStoredMessage(String msgId) throws DAOException{ MessageDAO msgDAO = (MessageDAO) TARGET.getDAOFactory().createDAO(MessageDAO.class); MessageDVO msgDVO = (MessageDVO) msgDAO.createDVO(); msgDVO.setMessageId(msgId); msgDVO.setMessageBox(MessageDVO.MSGBOX_OUT); msgDAO.retrieve(msgDVO); return msgDVO; } private String getFileName (String value){ String filename = null; String[] tokens = value.split(";"); if(tokens!= null && tokens.length > 1 && tokens[0].trim().equalsIgnoreCase("attachment")){ for(int index =1; index < tokens.length; index++){ if(tokens[index].trim().startsWith("filename")){ filename = tokens[index].substring(tokens[index].indexOf("=") +1); if(filename.trim().length() == 0){ filename = null; continue; } break; } } } return filename; } private String calculateMIC(byte[] content) throws Exception{ String digestAlg = (partnershipDVO.getMicAlgorithm() == null?PartnershipDVO.ALG_MIC_SHA1:partnershipDVO.getMicAlgorithm()); MessageDigest md = MessageDigest.getInstance(digestAlg, "BC"); md.update(content); byte[] digest = md.digest(); String digestString = new String(Base64.encode(digest)); return digestString + ", " + partnershipDVO.getMicAlgorithm(); } /** * Canonicalizes the given data by removing the starting new lines. * * @param data the data to be canonicalized. * @return the canonicalized data */ private byte[] canonicalize(byte[] data) { if (data == null) { data = new byte[]{}; } int pos = 0; for (int i=0; i+1<data.length; i+=2) { if (data[i] == '\r' && data[i+1] == '\n') { pos += 2; } else { break; } } byte[] canonicalized = new byte[(data.length - pos + 1)]; for(int i =0;pos <= data.length;pos++, i++){ canonicalized[i] = data[pos]; } return canonicalized; } }