如何在 BizTalk AS2 接收中使用“引用打印”内容传输编码?

Posted

技术标签:

【中文标题】如何在 BizTalk AS2 接收中使用“引用打印”内容传输编码?【英文标题】:How to use "quoted-printable" content-transfer-encoding with BizTalk AS2 receiving? 【发布时间】:2020-05-06 10:43:04 【问题描述】:

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

接收管道“Microsoft.BizTalk.EdiInt.DefaultPipelines.AS2Receive, Microsoft.BizTalk.Edi.EdiIntPipelines, Version=3.0.1.0, Culture=neutral,由于以下错误,PublicKeyToken=31bf3856ad364e35" 被暂停:不支持引用打印的内容传输编码。。 挂起消息的序号为2。

经过一番调查,我发现当附带的 XML 有效负载包含非 ASCII 时,相关贸易伙伴的 AS2 平台有时会将 MIME 正文部分的Content-Transfer-Encoding 设置为人物。发生这种情况时,消息会因上述错误而暂停(不可恢复)。

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

我也尝试过从贸易伙伴那里接收未加密的消息(通过双方协议),但似乎在这里做错了,并且传递给消息框的消息最终没有被正确反汇编(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 是否支持 quoted-printable 编码方法?如果是这样,我做错了什么?如果没有,我有哪些解决方法?

【问题讨论】:

【参考方案1】:

我已通过 Microsoft BizTalk 技术支持就该问题开具了票证。他们的回答是

MS BizTalk Server 2013R2 不支持引用的可打印编码,MS BizTalk Server 2020 很可能不支持

【讨论】:

【参考方案2】:

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

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

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”类的更多信息,请关注link。

2.将负载中的任何 quoted-printable 内容转换为普通的 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 + "--\r\n";

接下来我们在 MIME 正文部分标头中获取 `Content-Transfer-Encoding' 值:

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 共享的 this 实现。

3.更新Content-Type HTTP 标头和 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 方配置需要) 添加对压缩的支持

如果您想要源代码的副本,请给我留言,我会看到有关将其升级到在线存储库的信息。

【讨论】:

以上是关于如何在 BizTalk AS2 接收中使用“引用打印”内容传输编码?的主要内容,如果未能解决你的问题,请参考以下文章

Biztalk AS2开发经验总结

[BTS] BizTalk EDI AS2 Error 1

Biztalk AS2开发经验总结

BizTalk SOAP 接收位置性能不佳

如何将文件接收地址AS2 URL中的HTTP修改为HTTPS?

BizTalk 2016 sFTP WinSCP - 无法接收更多消息