带有 X.509 证书和 Java Web 服务的 WCF 客户端

问题描述:

我目前正在尝试开发一个与 3rd 方网络服务交互的客户端.第三方 Web 服务是用 Java 编写的,我们为它们提供了用于签署消息的 CA X509 证书.第 3 方指定 WS-Security 1.1 (http://docs.oasis-open.org/wss/v1.1/wss-v1.1-spec-os-SOAPMessageSecurity.pdf).

I'm currently trying to develop a client that interacts with a 3rd party web service. The third party web service is written in Java, and we have supplied them with a CA X509 certificate that is used to sign the messages. The 3rd party specifies WS-Security 1.1 (http://docs.oasis-open.org/wss/v1.1/wss-v1.1-spec-os-SOAPMessageSecurity.pdf).

我们能够通过 SoapUI 成功签署消息、发送和获得响应,但我无法在 WCF (.NET 4.5) 中获得相同的功能.我查看了从 WCF 客户端发送的消息(通过 SvcTraceViewer 和消息日志),看起来格式与 SoapUI 中的格式略有不同.

We are able to successfully sign the message, send and get a response through SoapUI, but I am having trouble to get the same functionality to work in WCF (.NET 4.5). I've looked at the message being sent from the WCF client (via SvcTraceViewer and the message logs), and it appears the format is slightly different from that in SoapUI.

当我发送消息时,我收到以下异常:

When I send the message, I get the following exception:

没有端点监听 https://;可以接受消息.这通常是由不正确的地址或 SOAP 操作引起的.有关详细信息,请参阅 InnerException(如果存在).

内部异常是无法连接远程服务器,内部异常是连接尝试失败,因为连接方在一段时间后没有正确响应,或建立连接失败,因为连接的主机未能响应XXX.XXX.XXX.XXX:443.

The inner exception is Unable to connect to the remote server, and the inner exception for that is A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond XXX.XXX.XXX.XXX:443.

由于端点已启动且可用,我认为我的绑定设置不完全正确.

Since the endpoint is up and available, I'm thinking my binding is not set up entirely correctly.

我在这里检查了关于 SO 的问题,包括使用 X.509 证书签署 SOAP 消息从 WCF 服务到 Java 网络服务 并阅读 Yaron Naveh 的 12 种常见的 wcf 互操作混淆 有帮助,但还没有让我一路走好.

I've checked questions here on SO, including Signing SOAP messages using X.509 certificate from WCF service to Java webservice and read Yaron Naveh's 12 common wcf interop confusions which have helped, but haven't gotten me all the way there yet.

绑定定义:

<customBinding>
  <binding name="myCustomBinding">
    <textMessageEncoding messageVersion="Soap11" />
    <security authenticationMode="MutualCertificate"
              defaultAlgorithmSuite="Basic128Sha256Rsa15"
              includeTimestamp="true"
              messageSecurityVersion="WSSecurity10WSTrustFebruary2005WSSecureConversationFebruary2005WSSecurityPolicy11BasicSecurityProfile10"
              securityHeaderLayout="LaxTimestampLast" />
    <httpsTransport />
  </binding>
</customBinding>

创建代理工厂的方法:

private Task<ChannelFactory<T>> CreateChannelFactory<T>(string bindingConfig, string address)
{

    EndpointAddress endpoint = new EndpointAddress(new Uri(address), EndpointIdentity.CreateDnsIdentity(<identity name>));
    ChannelFactory<T> proxy = new ChannelFactory<T>(new CustomBinding(bindingConfig), endpoint);
    proxy.Credentials.ClientCertificate.SetCertificate(storeLocation, storeName, findByType, findByValue);
    proxy.Credentials.ServiceCertificate.SetDefaultCertificate(storeLocation, storeName, findByType, findByValue);

    return Task.FromResult<ChannelFactory<T>>(proxy);
}

第三方实际上有多个服务(本质上每个服务一个操作合同),所以我在客户端开始时使用 async/await 为每个服务生成 ChannelFactory 对象code>,并且证书的值storeLocationstoreNamefindByTypefindByValue 全局存储在客户端中(以防万一有人想知道).

The 3rd party actually has several services (one operation contract per service, essentially), so I generate ChannelFactory<T> objects for each service at client start using async/await, and the values storeLocation, storeName, findByType and findByValue for the certificate are stored globally in the client (in case any one was wondering).

当我发送消息时,我从适当的工厂创建了一个频道.然后我得到了错误.

When I send the message, I create a channel from the appropriate factory. And then I get the error.

这是通过 SoapUI 发送的消息的相关部分(有效):

Here is the relevant part of the message sent via SoapUI (that works):

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" 
                  xmlns:view="http://<service namespace>">
  <soapenv:Header>
    <wsse:Security soapenv:mustUnderstand="1" 
                   xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" 
                   xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
      <wsse:BinarySecurityToken EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary" 
                                ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3" 
                                wsu:Id="X509-F4F47BCAA968D14D08143033737254925">(data)</wsse:BinarySecurityToken>
      <ds:Signature Id="SIG-F4F47BCAA968D14D08143033737254928" 
                    xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
        <ds:SignedInfo>
          <ds:CanonicalizationMethod  Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#">
            <ec:InclusiveNamespaces PrefixList="soapenv view" 
                                    xmlns:ec="http://www.w3.org/2001/10/xml-exc-c14n#"/>
          </ds:CanonicalizationMethod>
          <ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
          <ds:Reference URI="#TS-F4F47BCAA968D14D08143033737254624">
            <ds:Transforms>
              <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#">
                <ec:InclusiveNamespaces PrefixList="wsse soapenv view" 
                                      xmlns:ec="http://www.w3.org/2001/10/xml-exc-c14n#"/>
              </ds:Transform>
            </ds:Transforms>
            <ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
            <ds:DigestValue>hg5n7PfuAfYb/LEawatI4ZBK0wmy14+Y6DihGhgBMI4=</ds:DigestValue>
          </ds:Reference>
        </ds:SignedInfo>
        <ds:SignatureValue>(data)</ds:SignatureValue>
        <ds:KeyInfo Id="KI-F4F47BCAA968D14D08143033737254926">
          <wsse:SecurityTokenReference wsu:Id="STR-F4F47BCAA968D14D08143033737254927">
            <wsse:Reference URI="#X509-F4F47BCAA968D14D08143033737254925" 
                            ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3"/>
          </wsse:SecurityTokenReference>
        </ds:KeyInfo>
      </ds:Signature>
      <wsu:Timestamp wsu:Id="TS-F4F47BCAA968D14D08143033737254624">
        <wsu:Created>2015-04-29T19:56:12Z</wsu:Created>
        <wsu:Expires>2015-04-29T19:56:17Z</wsu:Expires>
      </wsu:Timestamp>
    </wsse:Security>
  </soapenv:Header>
  <soapenv:Body>  
  <!-- Not relevant.  Not signed -->
  </soapenv:Body>
</soapenv:Envelope>

以下是具有指定绑定的 WCF 生成的消息:

Here is the message produced by WCF with the specified bindings:

<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"
            xmlns:u="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
  <s:Header>
    <VsDebuggerCausalityData xmlns="http://schemas.microsoft.com/vstudio/diagnostics/servicemodelsink">uIDPo8j1TaWVCpRLgEy0S8UuwAcBAAAAAkQP7Rrb00auQ0G7/7Q0C1x7YNOf+kFOt9ioJVgbfFYACQAA</VsDebuggerCausalityData>
    <o:Security s:mustUnderstand="1" 
                xmlns:o="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
      <o:BinarySecurityToken>
        <!-- Removed-->
      </o:BinarySecurityToken>
      <Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
        <SignedInfo>
          <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"></CanonicalizationMethod>
          <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"></SignatureMethod>
          <Reference URI="#_1">
            <Transforms>
              <Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"></Transform>
            </Transforms>
            <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"></DigestMethod>
            <DigestValue>8KAbTZRA1cC60emKdKIiIm3zvv1jPPfVaia3a9l1c3g=</DigestValue>
          </Reference>
          <Reference URI="#uuid-5cb02cb6-0ae6-486d-ad2b-f9f9107b4576-2">
            <Transforms>
              <Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"></Transform>
            </Transforms>
            <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"></DigestMethod>
            <DigestValue>zR0k1GizuQekuM9WcSzVGssZowuzj3Dza/WGYmMqjSo=</DigestValue>
          </Reference>
        </SignedInfo>
        <SignatureValue>(data)</SignatureValue>
        <KeyInfo>
          <o:SecurityTokenReference>
            <o:Reference ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3" 
                         URI="#uuid-96e7c3a3-cbe9-409e-ad49-9dcc07ef4360-2"></o:Reference>
          </o:SecurityTokenReference>
        </KeyInfo>
      </Signature>
      <u:Timestamp u:Id="uuid-5cb02cb6-0ae6-486d-ad2b-f9f9107b4576-2">
        <u:Created>2015-04-30T18:00:28.886Z</u:Created>
        <u:Expires>2015-04-30T18:05:28.886Z</u:Expires>
      </u:Timestamp>
    </o:Security>
  </s:Header>
  <s:Body u:Id="_1" 
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  </s:Body>
</s:Envelope>

以下是 WCF 版本与 SoapUI 版本的差异/缺失项:

Here are differences/missing items from the WCF version compared to the SoapUI version:

  • 缺少 EncodingTypeValueTypeId属性.
  • 缺少 Id 属性.
  • 'CanonicalizationMethodmissing
  • 两个元素而不是 1 个.不确定 URI 属性的用途(在 WCF 版本中).
  • 缺少 Id 属性.
  • 缺少 Id 属性.
  • <BinarySecurityToken> missing EncodingType, ValueType and Id attributes.
  • <Signature> missing Id attribute.
  • 'CanonicalizationMethodmissing
  • <Reference> two elements instead of 1. Not sure what the URI attribute is for (in the WCF version).
  • <KeyInfo> missing Id attribute.
  • <SecurityTokenReference> missing Id attribute.

其他一切似乎都匹配(命名空间前缀及其分配位置除外).我更愿意通过自定义绑定来做到这一点,但如果我需要处理传出请求,我愿意实现 IClientMessageInspector.

Everything else seems to match up (with the exception of namespace prefixes and where they are assigned). I would prefer to do this via a custom binding, but I'm open to implementing IClientMessageInspector if I need to massage the outgoing request.

今天早上我解决了这个问题.手头有两个问题.

I was able to resolve this issue this morning. There were 2 issues at hand.

第一个似乎与我公司的 VPN 有关.昨天我能够在办公室发送和接收回复(而不是我发布问题的那一天,当我使用 VPN 时).昨晚再次在 VPN 上,我收到 没有端点在 https:// 侦听.可以接受消息.这通常是由不正确的地址或 SOAP 操作引起的.有关详细信息,请参阅 InnerException(如果存在). 再次出现异常,因此我将客户端部署到我们的其中一台开发服务器,并且能够再次成功通信.

The first appears to be related to my company's VPN. I was able to send and receive responses yesterday in the office (as opposed to the day I posted the question, when I was on VPN). Last night on the VPN again I was getting the There was no endpoint listening at https://<service addres> that could accept the message. This is often caused by an incorrect address or SOAP action. See InnerException, if present, for more details. exception again, so I deployed the client to one of our dev servers and was able to communicate successfully again.

一旦我能够发送消息,第二个问题就被发现了.我在跟踪消息日志中看到了响应,但收到了一个新的异常:

The second issue was discovered once I was able to send the message. I saw the response in the trace message logs, but I received a new exception:

安全处理器无法在消息中找到安全标头.这可能是因为消息是不安全的错误,或者因为通信双方之间存在绑定不匹配.如果服务配置为安全并且客户端未使用安全,则可能会发生这种情况.

请求已签名(由客户端),但响应未签名.将自定义绑定上的 enableUnsecuredResponse 属性设置为 true 解决了此问题.

The request is signed (by the client), but the response is unsigned. Setting the enableUnsecuredResponse attribute on the custom binding to true resolved this issue.

最终的自定义绑定配置如下所示:

The final custom binding configuration looks like this:

<customBinding>
    <textMessageEncoding messageVersion="1.1" />
    <security authenticationMode="MutualCertificate"
              defaultAlgorithmSuite="Basic128Sha256Rsa15"
              enableUnsecuredResponse="true"
              includeTimestamp="true"
              messageSecurityVersion="WSSecurity10WSTrustFebruary2005WSSecureConversationFebruary2005WSSecurityPolicy11BasicSecurityProfile10"
              securityHeaderLayout="LaxTimestampLast" />
    <httpsTransport /> 
</customBinding>

设置证书的代码没有变化,除了将名称传递给 EndpointIdentity.CreateDnsIdentity 可配置.

No changes in the code for setting the certificates, with the exception of making the name passed into EndpointIdentity.CreateDnsIdentity configurable.