/*
* 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;
}
}