Using AS2 Bridges in MABS (post August 2014 Update)

In my previous post about using the new EDI bridges in Microsoft Azure BizTalk Services (MABS), I promised to follow up with an example of using the AS2 bridge type – so here it is.

Applicability Statement 2 (AS2) is all about establishing security and reliability when transmitting B2B messages over HTTP/S. It provides for digital signing and encryption as well as acknowledgements via Message Disposition Notifications (MDN), which also leads to support for Non-Repudiation (NRR).

Like BizTalk Server, MABS provides support for AS2 with EDI transactions; in fact, the latest update has enhanced this experience with additional symmetric key encryptions including AES, DES3 and RC2 as well as MD5, SHA1 and SHA2 for MIC calculation. The main difference in this latest update, though, is that the AS2 bridge and the associated agreement are configured separately, just like the EDI bridges themselves now are. Understanding the sequencing of these bridges and how they connect is key, however, and may not be readily apparent from the current documentation – hence the reason for this article.

Scenario for Using AS2 Bridges

For the purposes of this post, I am going to expand on the demo used in the previous post by introducing AS2 bridges. I am not bothering to configure certificates, because this process has not changed since MABS was released, and it is well-documented on MSDN. Besides that, the focus of this article is on how the bridges connect, not so much about the implementation of encoding, etc. (But if you want to see how the certificates are used and the relationship of the public & private keys, I suggest having a look at this page which lays out the whole process in a very clear diagram.)

Let’s assume that Partner B requires the AS2 protocol for all B2B exchanges. The pattern in BizTalk Services therefore makes use of both EDI bridges and AS2 bridges, with separate agreements established for both:

AS2-Exchange

Here are the processing steps outlined in the above diagram:

1 Partner A (host partner) sends a Purchase Order (EDI 850) to the EDI Send Bridge where the “Send” settings of the EDI agreement are hosted.
2 After performing any configured transforms, promotions & lookups (none req’d in this demo), the bridge routes the message to the AS2 Send Bridge, where the AS2 agreement is hosted.
3 The AS2 Send Bridge performs any signing and/or encryption specified in the AS2 agreement “Send” settings and routes the message to the HTTPS endpoint for Partner B‘s service.
4 Partner B eventually sends back a Functional Acknowledgement (EDI 997) message to the published HTTPS endpoint of the AS2 Receive Bridge, where the “Receive” settings of the AS2 agreement are hosted.
5 After performing any decryption specified in the AS2 agreement, the AS2 Receive bridge routes the message to the EDI Receive Bridge.
6 The EDI Receive Bridge performs any configured transforms, promotions & lookups (none req’d in this demo) and routes the 997 message back to Partner A‘s configured system for receiving acknowledgements (which in this case is simply a Service Bus queue).

Notice that the AS2 bridges are placed at the point of contact with the guest partner (Partner B), both for the send of the Purchase Order (850) and the receive of the Functional Acknowledgement (997).

Let’s walk through the process of implementing this scenario (assumes you have completed the steps outlined in the previous post).

1. Create the AS2 Bridge

Just as you do with the EDI bridge, AS2 bridges are created within the BizTalk Services Management Console using the “Add” button at the bottom of the screen. In this case we choose the AS2 option from the drop-down menu:

01-CreateAS2Bridge

I’ve chosen to archive the decoded message here. This option is only available in certain editions of BizTalk Services, including Developer. Because we’re using the X12 protocol here, there’s no need to bother with the optional EDIFACT delimiter settings.

As the next four screenshots show, we set the inbound and outbound URLs for both the Receive and the Send aspects of the bridge. The AS2 Receive Setting are configured to forward the message to the EDI bridge (yes, I should have chosen a more descriptive name for this bridge instead of accepting the default GUID):

02-CreateAS2Bridge-Receive

03-CreateAS2Bridge-Receive

Meanwhile the AS2 Send Settings forward the message to the target guest partner service (which is this case is a mock AS2 service I’ve created as an Azure Web Site):

04-CreateAS2Bridge-Send

05-CreateAS2Bridge-Send

Once we have deployed this, we see that in fact two AS2 bridges have been created – the Send & Receive bridges are displayed separately in the list (but if you click either one, you’ll get to the same bridge configuration page):

06-Bridges

2. Create the AS2 Agreement

The next step is to create the agreement. Be sure to select the “AS2” option at the top menu first, so that when clicking the “Add” button an AS2 agreement is created and not the default EDI one:

07-CreateAgreement

The General page is where we specify the partners in the agreement, including the unique identifiers assigned to them. These identifiers do not have to be the same as the identifiers used in the EDI agreement, but it is important that they are unique within the MABS instance as they will be used to select this agreement when the message arrives at the bridge (meaning that these identifiers must be in the headers of the message):

07-CreateAgreement-General

This page also allows us to enable Non-Repudiation on Receipt (NRR) – which can be used to prove the authenticity and validity of a message if partners agree to employ this feature. Obviously we don’t need this within our demo here.

The Send and Receive pages allow us to specify:

  • the certificates used for signing & encryption
  • requirements for requesting or sending MDN, including optional MIC algorithm
  • optional message compression
  • HTTP header treatment

For more details about how to configure these pages, please refer to the MSDN documentation.

Once we save the agreement, it appears in the portal listing under the AS2 agreements page (see the screenshot at the beginning of this section).

3. Send the AS2 Identifiers in the Message Header

My previous post talked about the necessity of specifying agreement identifiers when sending messages to an EDI bridge; we need to do the same here as well.  Although now, it is not merely the EDI agreement identifier that is required, but the AS2-To and AS2-From identifiers are required as well (this is how the AS2 agreement will be selected when the EDI Send Bridge delivers the message to the AS2 Send Bridge). Again using our modified version of Sandro Pereira‘s excellent EAI Bridge Message Sender tool, we specify the EDI agreement ID and the AS2 identifiers when sending the request:

00-MessageSender

Test Results

Sending three separate instances of the message as indicated in the screenshot above successfully made the round-trips to the mock AS2 web site, storing the returned 997s in the configured Service Bus queue:

09-ServiceBusExplorer-01

Because we copied the control number from each 850 request into the appropriate fields of the 997 response, MABS was able to correlate the Functional Acknowledgement (FA) to the applicable 850 in tracking:

10-EDI-Tracking

The tracking also records the round trip through the AS2 bridge:

11-AS2-Tracking

Mock AS2 Web Site

If you are interested in how to mimic the functionality of a target system that accepts an EDI Purchase Order (850) and returns a 997, we can implement an ASP.NET web site in Azure (no UI is required here). My mock service simply performs two functions:

  • Saves the request message to a Service Bus Queue
  • Manufactures a 997 message with the appropriate control numbers, etc. by parsing information out of the request.

protected void Page_Load(object sender, EventArgs e)
{
try
{
if (Page.Request.RequestType == “POST”)
{
if (Page.Request.ContentLength == 0)
{
Page.Response.Write(“Error – content length 0”);
Page.Response.StatusCode = 500;
return;
}

            Stream outputStream = Page.Request.InputStream;

            SendToQueue(outputStream, Page.Request.Headers);
Send997(outputStream, Page.Request.Headers);

}
}
catch (Exception ex)
{
Page.Response.Write(“ERROR: ” + ex.Message);
}
}

The code for each of the highlighted supported methods above is shown below:

private void Send997(Stream outputStream, NameValueCollection headers)
{
StringBuilder sb = new StringBuilder();
string responseEndpoint = null;

    // Variables to get info from request
string senderID = null;
string receiverID = null;
string senderQualifier = null;
string receiverQualifier = null;
string controlNumber = null;
string firstLine = null;
string fgID = null;

    // Get the AS2 headers from the request
string as2To = headers[“AS2-To”];
string as2From = headers[“AS2-From”];

    bool usingAS2 = !String.IsNullOrEmpty(as2To) &&
!String.IsNullOrEmpty(as2From);

    if (usingAS2)
{
GetAS2HeadersFromRequest(outputStream, out controlNumber, out senderID, out receiverID, out senderQualifier, out receiverQualifier, out fgID, out firstLine);

        sb.Append(firstLine + “~”);
sb.Append(String.Format(“GS*FA*{0}*{1}*{2}*{3}*{4}*X*004010~”, senderID, receiverID, DateTime.Now.ToString(“yyyyMMdd”), DateTime.Now.ToString(“HHmm”), controlNumber));
sb.Append(String.Format(“ST*997*{0}~”, controlNumber.TrimStart(new char[] { ‘0’ }).PadLeft(4, ‘0’)));
sb.Append(String.Format(“AK1*{0}*{1}~AK9*A*1*1*1~SE*4*{2}~”, fgID, controlNumber.TrimStart(new char[]{‘0’}), controlNumber.TrimStart(new char[]{‘0’}).PadLeft(4, ‘0’)));
sb.Append(String.Format(“GE*1*{0}~IEA*1*{0}~”, controlNumber));

        responseEndpoint = ConfigurationManager.AppSettings[“TargetResponseEndpoint”];
SendToWebEndpoint(responseEndpoint, sb.ToString(), headers);
}
}

private void SendToWebEndpoint(string responseEndpoint, string request, NameValueCollection headers)
{
using (WebClient client = new WebClient())
{
client.BaseAddress = responseEndpoint;

        if (client.Headers == null)
{
client.Headers = new WebHeaderCollection();
}

        for (int i = 0; i < headers.AllKeys.Length; i++)
{
if (headers.AllKeys[i].StartsWith(“AS2”) || headers.AllKeys[i].ToUpper().Equals(“MESSAGE-ID”))
{
client.Headers.Add(headers.AllKeys[i], headers.GetValues(headers.AllKeys[i])[0]);
}
}

        ServicePointManager.ServerCertificateValidationCallback += (sender, certificate, chain, sslPolicyErrors) => true;
        string result = client.UploadString(responseEndpoint, request);
}
}

private void GetAS2HeadersFromRequest(Stream outputStream, out string controlNumber, out string senderID, out string receiverID, out string senderQualifier, out string receiverQualifier, out string functionalGroupID, out string firstLine)
{
string request = null;

    using (StreamReader sr = new StreamReader(outputStream))
{
outputStream.Position = 0;
request = sr.ReadToEnd();
}

    if (!String.IsNullOrEmpty(request))
{
string[] lines = request.Split(new char[] { ‘~’ });

        if (lines.Length > 1)
{
firstLine = lines[0];
string[] isaTokens = lines[0].Split(new char[] { ‘*’ });
if (isaTokens.Length == 17)
{
senderQualifier = isaTokens[5];
senderID = isaTokens[6].Trim();
receiverQualifier = isaTokens[7];
receiverID = isaTokens[8].Trim();
controlNumber = isaTokens[13];
}
else
{
throw new ApplicationException(“Delimiting ISA line by ‘*’ yielded unexpected number of tokens: ” + isaTokens.Length.ToString());
}

            string secondLine = lines[1];
string[] gsTokens = lines[1].Split(new char[] { ‘*’ });
if (gsTokens.Length > 1)
{
functionalGroupID = gsTokens[1];
}
else
{
throw new ApplicationException(“Delimiting GS line by ‘*’ yielded unexpected number of tokens: ” + isaTokens.Length.ToString());
}
}
else
{
throw new ApplicationException(“Delimiting request by ‘~’ yielded unexpected number of lines: ” + lines.Length.ToString());
}
}
else
{
throw new ApplicationException(“Request stream was null or empty.”);
}
}

The tricky bits are in understanding the following:

  • The required HTTP headers for sending a message directly to an AS2 bridge endpoint:
    • AS2-To
    • AS2-From
    • Message-ID
  • How to parse the necessary tokens out of the ISA and GS envelope in order to place them in the 997 response (required for correlation)
  • How to properly format the 997 response (this MSDN reference may help with the 997 itself, as well as this article to understand the envelope structure)
  • How to get around the SSL handshake considering that the certificate used by BizTalk Services is unlikely to be trusted by the web site

That last point was key one because unless you have configured MABS to use a custom certificate signed by a well-known trusted certification authority (e.g. Verisign, Thwarte, etc), you are likely to get this error:

The remote certificate is invalid according to the validation procedure.

Fortunately, Rene Brauwers has a good workaround for this (not suitable for production use but definitely a life-saver for demos like this). Note this line of code in the SendToWebEndpoint method:

ServicePointManager.ServerCertificateValidationCallback += (sender, certificate, chain, sslPolicyErrors) => true;

About Dan Toomey
Husband, father, Enterprise integration geek, Microsoft Azure MVP, Pluralsight author, Brisbane Azure User Group leader (@BrisbaneAzureUG), MCPD, MCT, MCTS & former professional musician.

One Response to Using AS2 Bridges in MABS (post August 2014 Update)

  1. Homie says:

    Great tutorial.. really help me get started to understand how things works..

    note on the https://msdn.microsoft.com/en-US/library/azure/dn794001.aspx
    in that document it says it requires “AS2-MessageID” in the header when you sending request to AS2-receive endpoint.

    After few frustration moment (and few lemonade cans) I found your blog and it says the header should be “Message-ID” instead of “AS2-MessageID”.

    awesomeness !!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

John Glisson - Geek of the Cloth

Thoughts on integration, technology and what-not...

prashantbiztalkblogs

My BizTalk Experiences

The CRUCIBLE

THINK: It's not illegal....yet.....

paulbouwer.com

life and technology

Abdul Rafay's BizTalk Blog

My experiences with BizTalk related to architecture, development and performance in my enterprise.

Mike Diiorio

Connected Systems and other thoughts

BizTalk musings

Issues, patterns and useful tips for BizTalk development

EAI Guy.net

Enterprise Applicaiton Integration and SOA 2.0

Connected Pawns

Mainly BizTalk & Little Chess

Man Vs. Machine

Why can't we all just get along?

Adventures inside the Message Box

BizTalk, Azure, and other tools in the Microsoft stack - Johann Cooper

Biz(Talk)2

Talk, talk and more talk about BizTalk

Richard Seroter's Architecture Musings

Blog Featuring Code, Thoughts, and Experiences with Software and Services

Sandro Pereira BizTalk Blog

My notes about BizTalk Server 2004, 2006, 2006 R2, 2009, 2010, 2013 and now also Windows Azure BizTalk Services.

BizTalk Events

Calendar of BizTalk events all over the world!

Mind Over Messaging

Musings on BizTalk, Azure, and Enterprise Integration

The WordPress.com Blog

The latest news on WordPress.com and the WordPress community.

%d bloggers like this: