如何将"quoted-printable"内容传输编码与 BizTalk AS2 接收一起使用?



我目前正在使用BizTalk Server 2013 R2与许多不同的贸易伙伴使用AS2交换EDI以及非EDI文档。我最近添加了一个新的贸易伙伴,在成功收到许多文件后,我开始看到这个错误不时发生:

由于以下错误,接收管道"Microsoft.BizTalk.EdiInt.DefaultPipelines.AS2Receive,Microsoft.Biztal.Edi.EdiIntPipelines,Version=3.0.1.0,Culture=neutral,PublicKeyToken=31bf3856a364e35"中组件"Microsoft.BizzTalk.EdiInt.PipelineComponents"的输出消息被挂起:不支持引用可打印的内容传输编码。挂起消息的序列号为2。

经过一些调查,我发现当所附的XML有效载荷包含非ASCII字符时,有问题的贸易伙伴的AS2平台有时会将MIME正文部分的Content-Transfer-Encoding设置为可引用。当这种情况发生时,消息将被挂起(不可恢复),并出现上述错误。

从该贸易伙伴收到的消息经过加密和签名,但没有压缩-并使用HTTP请求-响应(双向)端口接收,该端口配置有开箱即用的AS2Receive管道。我尝试过使用带有AS解码器、S/MIME解码器和AS2反汇编程序组件的自定义管道,但这似乎没有任何效果——错误保持不变。

我也尝试过从贸易伙伴那里接收未加密的消息(通过双方协议),但似乎在这里做了一些错误的事情,以及传递到MessageBox的消息最终没有被正确分解(MIME部分边界和AS2签名在实际的消息有效载荷中仍然可见)。由于贸易伙伴无论如何都不允许在生产环境中发送未加密的消息,我需要使用加密技术。他们也不能改变平台的行为,因为据报道,这将影响到他们所有的其他贸易伙伴。

以下是在挂起时收到的加密和签名的AS2消息的展开HTTP标头(省略号表示经过编辑的值):

Date: Mon, 20 Jan 2020 17:30:53 GMT
Content-Length: 8014
Content-Type: application/pkcs7-mime; name="smime.p7m"; smime-type=enveloped-data
From: ...
Host: ...
User-Agent: Jakarta Commons-HttpClient/3.1
AS2-To: ...
Subject: AS2 Message from ... to ...
Message-Id: <1C20200120-173053-740219@xxx.xxx.130.163>
Disposition-Notification-To: <mailto:...> ...
Disposition-Notification-Options: signed-receipt-protocol=optional, pkcs7-signature; signed-receipt-micalg=optional, sha1
AS2-From: ...
AS2-Version: 1.1
content-disposition: attachment; filename="smime.p7m"
X-Original-URL: /as2


以下是未加密的(省略号表示经过编辑的内容)有效载荷,当从源方发送完全相同的消息而不加密时:

------=_Part_16155_1587439544.1579506174880
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: quoted-printable
...
------=_Part_16155_1587439544.1579506174880
Content-Type: application/pkcs7-signature; name=smime.p7s; smime-type=signed-data
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename="smime.p7s"
Content-Description: S/MIME Cryptographic Signature
...
------=_Part_16155_1587439544.1579506174880--


问题:BizTalk Server是否支持带引号的可打印编码方法?如果是,我做错了什么?如果没有,我有什么变通办法?

对于其他可能遇到同样问题的人,我想我会分享我最终得到的解决方案。

由于错误是在AS2接收管道处理过程中遇到的,因此我的解决方案自然集中在创建一个自定义接收管道组件上,该组件的功能与开箱即用的AS2解码器组件大致相同,但支持引用的可打印编码方法:


1.解码和解密CMS/PKCS#7数据信封

这实际上是最简单的步骤,只有5行代码:

EnvelopedCms envelopedCms = new EnvelopedCms();
envelopedCms.Decode(encryptedData);
envelopedCms.Decrypt();
byte[] decryptedData = envelopedCms.Encode();
string decryptedMessageString = Encoding.ASCII.GetString(decryptedData);

-encryptedData是从HTTP适配器接收的AS2消息的正文部分数据流实例化的字节数组。

-Decrypt方法自动在用户和计算机证书存储中搜索适当的证书私钥,并使用该私钥解密AS2有效载荷。有关"EnvelopedCms"类的详细信息,请访问此链接。


2.将有效负载中任何引用的可打印内容转换为普通UTF-8文本

首先,我们必须从解密有效载荷开头的内容类型字符串中获取MIME边界名称:

int firstBlankLineInMessage = decryptedMessageString.IndexOf(Environment.NewLine + Environment.NewLine);
string contentType = decryptedMessageString.Substring(0, firstBlankLineInMessage);
Regex boundaryRegex = new Regex("boundary="(?<boundary>.*)"");
Match boundaryMatch = boundaryRegex.Match(contentType);
if (!boundaryMatch.Success)
throw new Exception("Failed to get boundary name from content type");
string boundary = "--" + boundaryMatch.Groups["boundary"].Value;

然后,我们拆分信封并重新合并,不包含内容类型标题部分:

string[] messageParts = decryptedMessageString.Split(new string[] {boundary}, StringSplitOptions.RemoveEmptyEntries);
string signedMessageString = boundary + messageParts[1] + boundary + messageParts[2] + boundary + "--rn";

接下来,我们在MIME正文部分标头中获得"内容传输编码"值:

int firstBlankLineInBodyPart = messageParts[1].IndexOf(Environment.NewLine + Environment.NewLine);
string partHeaders = messageParts[1].Substring(0, firstBlankLineInBodyPart);
Regex cteRegex = new Regex("Content-Transfer-Encoding: (?<cte>.*)");
Match cteMatch = cteRegex.Match(partHeaders);
if (!cteMatch.Success)
throw new Exception("Failed to get CTE from body part headers");
string cte = cteMatch.Groups["cte"].Value;
string payload = messageParts[1].Substring(firstBlankLineInBodyPart).Trim(); 

最后,我们检查CTE并在必要时解码:

string payload = messageParts[1].Substring(firstBlankLineInBodyPart).Trim();
if (cte == "quoted-printable")
{
// Get charset
Regex charsetRegex = new Regex("Content-Type: .*charset=(?<charset>.*)");
Match charsetMatch = charsetRegex.Match(partHeaders);
if (!charsetMatch.Success)
throw new Exception("Failed to get charset from body part headers");
string charset = charsetMatch.Groups["charset"].Value;
QuotedPrintableDecode(payload, charset);
}

注意:有很多不同的QP解码实现,包括一个.NET实现,据报道,一些用户发现它有缺陷。我决定使用Gonzalo共享的这个实现


3.更新Content-TypeHTTP头和BizTalk消息体部分流

string httpHeaders = objHttpHeaders.ToString().Replace("Content-Type: application/pkcs7-mime; name="smime.p7m"; smime-type=enveloped-data", "Content-Type: application/xml");
inMessage.Context.Write("InboundHttpHeaders", "http://schemas.microsoft.com/BizTalk/2003/http-properties", httpHeaders);
MemoryStream payloadStream = new MemoryStream(Encoding.UTF8.GetBytes(payload));
payloadStream.Seek(0, SeekOrigin.Begin);
pipelineContext.ResourceTracker.AddResource(payloadStream);
inMessage.BodyPart.Data = payloadStream;

-pipelineContext是传递给自定义管道组件的Execute方法的IPipelineContext变量
-inMessage为传递给Execute方法的IBaseMessage变量


最后的想法

上面的代码仍然可以通过多种方式进行改进:

  • 在尝试解密之前检查HTTP标头是否加密
  • 在将消息传递给AS2反汇编程序组件之前重新加密有效负载(如果BizTalk参与方配置需要)
  • 增加对压缩的支持

如果您想要源代码的副本,请给我一条消息,我会查看是否将其添加到在线回购中。

我已与Microsoft BizTalk技术支持部门就该问题打开了票证。他们的反应是

MS BizTalk Server 2013R2不支持引用的可打印编码";并且很可能不受MS BizTalk Server 2020 的支持

最新更新