WCF SOAP 1.1 和 WS-Security 1.0、客户端证书传输身份验证、消息正文签名的服务证书、用户名令牌、密码摘要、随机数

Posted

技术标签:

【中文标题】WCF SOAP 1.1 和 WS-Security 1.0、客户端证书传输身份验证、消息正文签名的服务证书、用户名令牌、密码摘要、随机数【英文标题】:WCF SOAP 1.1 and WS-Security 1.0, client certificate transport auth, service cert for message body signature, UsernameToken, Password Digest, Nonce 【发布时间】:2013-01-22 07:38:10 【问题描述】:

总结: 我正在使用 .NET 4.0 WCF 客户端使用 SOAP 1.1 和 WS-Security 1.0 使用 Web 服务(另一端的 DataPower、Java 服务)。 WCF 客户端必须为传输层的相互身份验证实现客户端证书。消息正文需要使用单独的服务/签名证书进行签名。 SOAP 标头还需要包含带有密码摘要的用户名令牌,并包含 Nonce 和 Created 标记。

我可以使用带有 BasicHTTPBinding 的 WSE 3.0 使用此 Web 服务。但到目前为止,我还没有成功地使用 WSHttpBinding 或 CustomBinding 实现 WCF。我已经尝试了所有的安全绑定元素,但到目前为止都没有运气。

我也在使用此处的 usernametoken 库 (http://blogs.msdn.com/b/aszego/archive/2010/06/24/usernametoken-profile-vs-wcf.aspx),因此我可以在 SOAP 标头的 UsernameToken 中添加密码摘要/nonce/created。

我目前正在使用 SecurityBindingElement.CreateMutualCertificateBindingElement 我还尝试了其他几个,例如 AsymmetricSecurityBindingElement、TransportSecurityBindingElement 等(在下面的代码中注释掉)

证书: 我使用 MMC 将客户端证书和服务证书都加载到证书存储中(顺便说一句,我在 Windows 7 上)。客户端证书和服务证书都有私钥。我已将 PFX 文件加载到 LocalMachine/Personal、LocalMachine/Root 和 LocalMachine/TrustedPeople。我还运行了 FindPrivateKey/ICACLS 来授予“IIS App Pool/DefaultAppPool”帐户的权限。虽然这些都不重要,因为我可以从我的机器上运行 WSE 3.0 代码,并且它可以在没有任何证书问题的情况下运行。

命令运行:

FindPrivateKey.exe My LocalMachine -t "thumbprint of client cert"
FindPrivateKey.exe My LocalMachine -t "thumbprint of service cert"
icacls C:\ProgramData\Microsoft\Crypto\RSA\MachineKeys\privateKeyOfClientCert /grant "IIS AppPool\DefaultAppPool":R      <<Successfully processed 1 files; Failed processing 0 files>>
icacls C:\ProgramData\Microsoft\Crypto\RSA\MachineKeys\privateKeyOfServiceCert /grant "IIS AppPool\DefaultAppPool":R     <<Successfully processed 1 files; Failed processing 0 files>>

WCF 问题: 我目前收到来自 DataPower 网关的“无法为具有权限 'x.x.com' 的 SSL/TLS 建立安全通道”消息。我认为这可能是因为网关正在获取服务证书并将其用于客户端身份验证,而不是使用我发送的客户端证书。我这样说是因为当我没有为端点指定 DNS 身份时,我收到一条消息,说网关期望 DNS 身份是“服务的主题名称/签名证书”。

这是由 WCF 生成的 SOAP 请求,它给出了上述错误。 WCF SOAP 请求看起来与 WSE SOAP 请求非常相似。上述错误很可能是由于 SSL/Transport 层的证书问题而发生的。

WCF SOAP 请求:

<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>
    <o:Security s:mustUnderstand="1" xmlns:o="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
        <u:Timestamp u:Id="uuid-8533d9a5-865e-4a4b-a750-fadb7c1ce36c-1">
            <u:Created>2013-02-06T20:53:04.679Z</u:Created>
            <u:Expires>2013-02-06T20:58:04.679Z</u:Expires>
        </u:Timestamp>
        <o:BinarySecurityToken u:Id="uuid-0bab08ce-3e3b-4360-a44b-694b06a3dd67-2" ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3">Removed Service Cert Encoded Value</o:BinarySecurityToken>
        <wsse:UsernameToken wsu:Id="7843ab92-f69a-4d00-a5ba-117e32a74f49" 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:Username>USER_Removed</wsse:Username>
            <wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest">XXX=</wsse:Password>
            <wsse:Nonce>XXX==</wsse:Nonce>
            <wsu:Created>2013-02-06T20:53:04Z</wsu:Created>
        </wsse:UsernameToken>
        <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/2000/09/xmldsig#rsa-sha1"></SignatureMethod>
                <Reference URI="#_1">
                    <Transforms>
                        <Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"></Transform>
                    </Transforms>
                    <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"></DigestMethod>
                    <DigestValue>XXX=</DigestValue>
                </Reference>
                <Reference URI="#uuid-8533d9a5-865e-4a4b-a750-fadb7c1ce36c-1">
                    <Transforms>
                        <Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"></Transform>
                    </Transforms>
                    <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"></DigestMethod>
                    <DigestValue>XXX=</DigestValue>
                </Reference>
                <Reference URI="#7843ab92-f69a-4d00-a5ba-117e32a74f49">
                    <Transforms>
                        <Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"></Transform>
                    </Transforms>
                    <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"></DigestMethod>
                    <DigestValue>XXX=</DigestValue>
                </Reference>
            </SignedInfo>
            <SignatureValue>XXXLongXXX=</SignatureValue>
            <KeyInfo>
                <o:SecurityTokenReference>
                    <o:Reference URI="#uuid-0bab08ce-3e3b-4360-a44b-694b06a3dd67-2"></o:Reference>
                </o:SecurityTokenReference>
            </KeyInfo>
        </Signature>
    </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">
    <ping xmlns="https://x.x.com/xxx/v1">
        <pingRequest xmlns="">hello</pingRequest>
    </ping>
</s:Body>

WSE 3.0 SOAP 请求(可行):

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing"
           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">
<soap:Header>
    <wsa:Action wsu:Id="Id-4271fb72-464a-467d-ab1f-4d32542e20f0"/>
    <wsa:MessageID wsu:Id="Id-11657f64-d856-47d8-b600-d5379fb91a0d">urn:uuid:ff8becb7-74c2-4844-ab46-8ae23f1355a7</wsa:MessageID>
    <wsa:ReplyTo wsu:Id="Id-40b2e6e8-e67b-4a6c-a545-071ce0f0107a">
        <wsa:Address>http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</wsa:Address>
    </wsa:ReplyTo>
    <wsa:To wsu:Id="Id-d5e0b488-6f8a-479c-940d-2b85833dbc66">https://x.x.com/xxx/v1</wsa:To>
    <wsse:Security soap:mustUnderstand="1">
        <wsu:Timestamp wsu:Id="Timestamp-68476551-5c58-4a47-967b-54ec18257b1b">
            <wsu:Created>2013-02-06T19:38:39Z</wsu:Created>
            <wsu:Expires>2013-02-06T19:43:39Z</wsu:Expires>
        </wsu:Timestamp>
        <wsse:UsernameToken wsu:Id="SecurityToken-e5f65166-a825-48cb-a939-8e515a637e01">
            <wsse:Username>USER_Removed</wsse:Username>
            <wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest">XXX=</wsse:Password>
            <wsse:Nonce>XXX==</wsse:Nonce>
            <wsu:Created>2013-02-06T19:38:39Z</wsu:Created>
        </wsse:UsernameToken>
        <Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
            <SignedInfo>
                <ds:CanonicalizationMethod xmlns:ds="http://www.w3.org/2000/09/xmldsig#" Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
                <SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
                <Reference URI="#Id-4271fb72-464a-467d-ab1f-4d32542e20f0">
                    <Transforms>
                        <Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
                    </Transforms>
                    <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
                    <DigestValue>XXX=</DigestValue>
                </Reference>
                <Reference URI="#Id-11657f64-d856-47d8-b600-d5379fb91a0d">
                    <Transforms>
                        <Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
                    </Transforms>
                    <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
                    <DigestValue>XXX=</DigestValue>
                </Reference>
                <Reference URI="#Id-40b2e6e8-e67b-4a6c-a545-071ce0f0107a">
                    <Transforms>
                        <Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
                    </Transforms>
                    <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
                    <DigestValue>XXX=</DigestValue>
                </Reference>
                <Reference URI="#Id-d5e0b488-6f8a-479c-940d-2b85833dbc66">
                    <Transforms>
                        <Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
                    </Transforms>
                    <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
                    <DigestValue>XXX=</DigestValue>
                </Reference>
                <Reference URI="#Timestamp-68476551-5c58-4a47-967b-54ec18257b1b">
                    <Transforms>
                        <Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
                    </Transforms>
                    <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
                    <DigestValue>XXX=</DigestValue>
                </Reference>
                <Reference URI="#Id-6f76e50e-932c-4878-bbc0-3ef4c8a36990">
                    <Transforms>
                        <Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
                    </Transforms>
                    <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
                    <DigestValue>XXX=</DigestValue>
                </Reference>
            </SignedInfo>
            <SignatureValue>XXXLongXXX=</SignatureValue>
            <KeyInfo>
                <wsse:SecurityTokenReference>
                    <wsse:KeyIdentifier ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509SubjectKeyIdentifier"
                                        EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary">XXX=</wsse:KeyIdentifier>
                </wsse:SecurityTokenReference>
            </KeyInfo>
        </Signature>
    </wsse:Security>
</soap:Header>
<soap:Body wsu:Id="Id-6f76e50e-932c-4878-bbc0-3ef4c8a36990">
    <ping xmlns="https://x.x.com/xxx/v1">
        <pingRequest xmlns="">hello</pingRequest>
    </ping>
</soap:Body>

这里是所有配置,请告诉我我做错了什么!

WCF web.config:当我在代码中进行所有配置时,我从 web.config 中删除了所有内容。

代码中的 WCF 配置:

var proxy = GetProxy();
pingResponseMessage resp = proxy.ping("hello");
lblStatus.Text = resp.status.ToString();

private XXXClient GetProxy()


    System.Net.ServicePointManager.ServerCertificateValidationCallback += (se, cert, chain, sslerror) =>  return true; ;

    XXXClient proxy = new XXXClient(GetCustomBinding(), new EndpointAddress(new Uri("https://xxx"), EndpointIdentity.CreateDnsIdentity("I am forced to put the signing cert subject here, nothing else works"), new AddressHeaderCollection()));

    proxy.Endpoint.Behaviors.Remove(typeof(ClientCredentials));
    proxy.Endpoint.Behaviors.Add(new UsernameClientCredentials(new UsernameInfo(@"USER_Removed", "X")));

    proxy.ClientCredentials.ClientCertificate.SetCertificate(StoreLocation.LocalMachine, StoreName.My, X509FindType.FindByThumbprint, "REMOVED");
    proxy.ClientCredentials.ServiceCertificate.SetDefaultCertificate(StoreLocation.LocalMachine, StoreName.My, X509FindType.FindByThumbprint, "REMOVED");
    proxy.ClientCredentials.ServiceCertificate.Authentication.CertificateValidationMode = System.ServiceModel.Security.X509CertificateValidationMode.None;

    return proxy;


private Binding GetCustomBinding()

    //TransportSecurityBindingElement secBE = SecurityBindingElement.CreateCertificateOverTransportBindingElement(MessageSecurityVersion.WSSecurity10WSTrust13WSSecureConversation13WSSecurityPolicy12BasicSecurityProfile10);
    //AsymmetricSecurityBindingElement secBE = (AsymmetricSecurityBindingElement)SecurityBindingElement.CreateMutualCertificateBindingElement(MessageSecurityVersion.WSSecurity10WSTrustFebruary2005WSSecureConversationFebruary2005WSSecurityPolicy11BasicSecurityProfile10);
    //secBE.InitiatorTokenParameters = new System.ServiceModel.Security.Tokens.X509SecurityTokenParameters  InclusionMode = SecurityTokenInclusionMode.AlwaysToRecipient, RequireDerivedKeys = false, X509ReferenceStyle = X509KeyIdentifierClauseType.SubjectKeyIdentifier ;
    //secBE.RecipientTokenParameters = new System.ServiceModel.Security.Tokens.X509SecurityTokenParameters  InclusionMode = SecurityTokenInclusionMode.AlwaysToInitiator, RequireDerivedKeys = false, X509ReferenceStyle = X509KeyIdentifierClauseType.SubjectKeyIdentifier ;
    //secBE.MessageProtectionOrder = System.ServiceModel.Security.MessageProtectionOrder.SignBeforeEncrypt;
    //secBE.EndpointSupportingTokenParameters.Signed.Add(new UserNameSecurityTokenParameters()  InclusionMode = SecurityTokenInclusionMode.AlwaysToRecipient, RequireDerivedKeys = false );
    //secBE.EndpointSupportingTokenParameters.Signed.Add(new X509SecurityTokenParameters(X509KeyIdentifierClauseType.SubjectKeyIdentifier, SecurityTokenInclusionMode.Never)  InclusionMode = SecurityTokenInclusionMode.Never, RequireDerivedKeys = false, X509ReferenceStyle = X509KeyIdentifierClauseType.SubjectKeyIdentifier );
    //secBE.ProtectionTokenParameters = new System.ServiceModel.Security.Tokens.X509SecurityTokenParameters  InclusionMode = SecurityTokenInclusionMode.AlwaysToRecipient ;
    //secBE.DefaultAlgorithmSuite = new CustomSecurityAlgorithm();

    SecurityBindingElement secBE = SecurityBindingElement.CreateMutualCertificateBindingElement(MessageSecurityVersion.WSSecurity10WSTrustFebruary2005WSSecureConversationFebruary2005WSSecurityPolicy11BasicSecurityProfile10);
    secBE.MessageSecurityVersion = MessageSecurityVersion.WSSecurity10WSTrustFebruary2005WSSecureConversationFebruary2005WSSecurityPolicy11BasicSecurityProfile10;
    secBE.EndpointSupportingTokenParameters.Signed.Add(new UsernameTokenParameters()  InclusionMode= SecurityTokenInclusionMode.AlwaysToRecipient, ReferenceStyle = SecurityTokenReferenceStyle.External, RequireDerivedKeys = false );
    secBE.SecurityHeaderLayout = SecurityHeaderLayout.Strict;
    //secBE.AllowInsecureTransport = false;
    //secBE.AllowSerializedSigningTokenOnReply = false;
    secBE.EnableUnsecuredResponse = true;
   secBE.IncludeTimestamp = true;
    secBE.SetKeyDerivation(false);

    TextMessageEncodingBindingElement textEncBE = new TextMessageEncodingBindingElement(MessageVersion.Soap11, System.Text.Encoding.UTF8);

    HttpsTransportBindingElement httpsBE = new HttpsTransportBindingElement();
    httpsBE.RequireClientCertificate = true;
    //httpsBindingElement.AllowCookies = false;
    //httpsBindingElement.AuthenticationScheme = System.Net.AuthenticationSchemes.Basic;
    httpsBE.BypassProxyOnLocal = false;
    httpsBE.HostNameComparisonMode = HostNameComparisonMode.StrongWildcard;
    //httpsBindingElement.KeepAliveEnabled = false;
    httpsBE.TransferMode = TransferMode.Buffered;
    httpsBE.UseDefaultWebProxy = true;

    CustomBinding myBinding = new CustomBinding();
    myBinding.Elements.Add(secBE);
    myBinding.Elements.Add(textEncBE);
    myBinding.Elements.Add(httpsBE);

    return myBinding;

我在 ServiceContract 和 OperationContracts 上添加了 ProtectionLevel.Sign,因为我只需要对消息正文进行签名。不过,我还没有到这一步来验证它。

[System.ServiceModel.ServiceContractAttribute(Namespace = "https://x.x.com/xxx/v1", ConfigurationName = "x.x", ProtectionLevel = System.Net.Security.ProtectionLevel.Sign)]
public interface XXXService 
    [System.ServiceModel.OperationContractAttribute(Action = "", ReplyAction = "*", ProtectionLevel = System.Net.Security.ProtectionLevel.Sign)]
    [System.ServiceModel.XmlSerializerFormatAttribute(SupportFaults=true)]
    [return: System.ServiceModel.MessageParameterAttribute(Name="return")]
    XXX.pingResponse ping(XXX.ping request);

[System.ServiceModel.ServiceContractAttribute(Namespace = "https://x.x.com/xxx/v1", ProtectionLevel = System.Net.Security.ProtectionLevel.Sign)]
public partial class XXXClient : System.ServiceModel.ClientBase<XXXService> 
    [System.ServiceModel.OperationContractAttribute(Action = "", ReplyAction = "*", ProtectionLevel = System.Net.Security.ProtectionLevel.Sign)]
    public XXX.pingResponseMessage ping(string pingRequest) 

我已将以下内容添加到 web.config 以允许记录整个soap,包括 pii 数据

(for pii, also added <machineSettings enableLoggingKnownPii="true" /> under <system.serviceModel> to C:\Windows\Microsoft.NET\Framework\vX\CONFIG\machine.config)

<system.serviceModel>
<diagnostics>
  <messageLogging logKnownPii="true" logEntireMessage="true" logMalformedMessages="true" logMessagesAtServiceLevel="true" logMessagesAtTransportLevel="true" maxMessagesToLog="3000"/>
</diagnostics>
</system.serviceModel>
<system.diagnostics>
<sources>
  <source name="System.ServiceModel.MessageLogging" logKnownPii="true">
    <listeners>
      <add initializeData="C:\trace.log" type="System.Diagnostics.XmlWriterTraceListener" name="messages"/>
    </listeners>
  </source>
</sources>
</system.diagnostics>

================

WSE 3.0(工作配置和代码): 网络配置:

<system.serviceModel>
<bindings>
  <basicHttpBinding>
    <binding name="myBinding" closeTimeout="00:01:00" openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00" allowCookies="false" bypassProxyOnLocal="false" hostNameComparisonMode="StrongWildcard" maxBufferSize="65536" maxBufferPoolSize="524288" maxReceivedMessageSize="65536" messageEncoding="Text" textEncoding="utf-8" transferMode="Buffered" useDefaultWebProxy="true">
      <readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384" maxBytesPerRead="4096" maxNameTableCharCount="16384"/>
      <security mode="Transport">
        <transport clientCredentialType="None" proxyCredentialType="None" realm=""/>
        <message clientCredentialType="UserName" algorithmSuite="Default"/>
      </security>
    </binding>
  </basicHttpBinding>
</bindings>
<client>
  <endpoint address="https://x.x.com/xxx/v1" binding="basicHttpBinding" bindingConfiguration="myBinding" contract="XXXService" name="XXX"/>
</client>
</system.serviceModel>
<appSettings>
<add key="XXXImplService" value="https://x.x.com/xxx/v1"/>
</appSettings>

...和 ​​WSE3 代码:

var proxy = new XXXImplServiceWse();

UsernameToken usernameToken = new UsernameToken(@"USER_Removed", "X");
proxy.RequestSoapContext.Security.Tokens.Add(usernameToken);

X509Certificate2 mutualCert = LoadCertFromStore(StoreLocation.LocalMachine, StoreName.My, "Client Cert Subject Name");
proxy.ClientCertificates.Add(mutualCert);

X509Certificate2 signCert = LoadCertFromStore(StoreLocation.LocalMachine, StoreName.My, "Service Cert Subject Name");

X509SecurityToken signatureToken = new X509SecurityToken(signCert);

MessageSignature signature = new MessageSignature(signatureToken); // <!-- IS THIS SAME AS THIS STEP IN WCF: secBE.EndpointSupportingTokenParameters.Signed.Add(new UsernameTokenParameters()) -->

proxy.RequestSoapContext.Security.Elements.Add(signature);

===========

那么,如何将上述 WSE 3.0 代码转换为 WCF?

【问题讨论】:

当您在 WSE 3.0 中有有效的解决方案时,为什么要首先进行转换?对于 WSE 代码中的问题:没有端点支持令牌是完全不同的功能。顺便提一句。是否还需要 WS-Addressing(在 WSE 客户端中使用)。 我们有一个客户坚持我们为他们提供 WCF 版本,因为他们说 WSE 3.0 是“旧技术”......我不认为解决问题,但肯定只是为了符合要求,我们'会保留 Soap11WSAddressing10 我将在周末研究这个问题 - 我自己很感兴趣,但要做好准备,WCF 可能没有解决方案(手动编写安全性除外) - WCF 不包括以前在 WSE 中可用的所有选项3.0。出于好奇:您是否有 WS-SecurityPolicy(通常是 WSDL 或 Java 服务部署的一部分)或服务的示例请求和响应(不是 WSE 生成的,而是 Java 客户端生成的)? 我浏览了 WSDL,它没有指定任何安全参数。由于我正在连接到 DataPower(刚刚发现它是 IBM DataPower xi50 v3.8.1) - 这是预期的安全性:网关至少需要: • SSL 客户端证书 • 签名正文 • wsse 用户名和密码标头 •随机数 • 时间戳 1.网关需要传输层的 SSL 客户端证书。该证书仅用于最初对客户端进行身份验证。 2. 然后它会检查消息体是否由消息层的 SOAP 签名证书签名。 3. 然后它将检查用户名令牌。 4. 然后网关将检查正在执行的 SOAP 操作是否是经过身份验证的客户端允许的操作。 5. 如果都满足,则设置路由到后端,并将请求发送到应用服务器。 【参考方案1】:

我浏览了您的代码,它看起来是正确的。 WSE 和 WCF SOAP 消息的区别很小,但区别仅在于用于签署消息的证书的引用方式。

我认为这里的核心问题是证书的错误使用。您正在使用传输和消息相互安全。理论上,这需要四个证书。你需要

用于传输安全的服务证书 - 服务器使用此证书来建立 SSL 连接。要成功建立连接,客户端必须信任证书(您需要信任颁发证书的机构,或者服务器证书必须放在您信任的人员存储中)。 用于传输安全的客户端证书 - 此证书用于在传输级别对服务器上的客户端进行身份验证 - 您的个人存储中必须有证书及其私钥 用于消息安全的服务证书 - 此证书用于加密请求和签名响应(当 WS-Security 1.0 时)。您需要在计算机上的某个位置拥有此证书(由您决定使用哪个位置来加载证书)。 用于消息安全的客户端证书 - 此证书用于加密响应和签名请求(当 WS-Security 1.0 时)。您需要在计算机上的某个位置拥有此证书及其私钥(由您决定使用哪个位置来加载证书)。

看起来您只有两个证书 - 一个客户端和一个服务器。在这种情况下,它们可能应该用于传输和消息安全。但是这里出现了一个有趣的问题 - 您在 WSE 示例中客户端的“签名”证书实际上是服务证书。如果确实如此,则意味着客户端必须有权访问服务器的私钥——这永远不会发生。这是对 PKI 基础设施的最严重违反。 PKI 基础设施基于对证书颁发机构的信任和保护私钥,其中每个参与者都有自己的私钥,其他人无法访问。共享私钥会降低安全性。在最坏的情况下,它可能等于根本没有安全性,因为任何有权访问私钥的人都可以拦截通信或消息上的虚假签名。

如果我是对的,您应该使用 WSE 3.0 并对此感到满意。仅仅强制 WCF 为 HTTPS 和消息安全使用不同的客户端证书可能非常困难。您有单个 ClientCertificate 属性,但您需要为 HTTPS 和消息安全加载不同的证书。它需要创建具有两个属性的自定义 ClientCredentials 和自定义 SecurityTokenManager 以返回正确的证书提供程序(通过实现每种用法(这是一个理论 - 我从未尝试过)。

顺便说一句。您对EndpointIdentity 的问题是基于您的服务暴露在某些 DNS 上的事实,并且如果服务证书(在您的情况下也是签名证书)中的主题不同,您必须为您的端点创建一个新的 DNS 身份。否则 WCF 将不信任证书。服务器证书应与用于访问服务器的 DNS 名称匹配的主题颁发。

【讨论】:

感谢您对此 Ladislav 的反馈和帮助。我的问题解决了。我不确定是什么解决了它。我在 IBM 网站上找到了以下内容,并将其与我们的 IT 网关基础架构小组一起提出。他们说没有对 DataPower 进行任何更改,但它今天早上开始工作!!!我将使用工作代码以及我放在一起的 CustomCredential 类来回答这个问题。谢谢! IBM: publib.boulder.ibm.com/infocenter/ieduasst/v1r1m0/… 当使用带有 SSL 的 BasicHttpBinding 时:您可以使用 disable-ssl-cipher-check 参数来禁用任何 TransportBinding 断言的密码检查。默认情况下,Web 服务代理不支持 Basic Auth Header。要与 WCF 互操作,需要自定义配置错误规则以注入 WWW-Authenticate 标头。【参考方案2】:

我能够解决我的问题并使用以下 WCF CustomBinding (CertificateOverTransport) 和 CustomCredentials(带有密码摘要的用户名令牌、传输身份验证的客户端证书和消息正文签名的服务证书)连接到 DataPower (IBM Xi50) Web 服务网关。 )我不确定究竟是什么解决了这个问题,但这是我的工作 WCF 代码!我希望这可以帮助与我处于类似情况的其他人。

请确认 DataPower Xi50 网关也为 WCF 配置。来自 IBM:“将 BasicHttpBinding 与 SSL 一起使用时:您可以使用 disable-ssl-cipher-check 参数来禁用任何 TransportBinding 断言的密码检查。Web 服务代理默认不支持 Basic Auth Header。需要注入 WWW-Authenticate 标头的错误规则才能与 WCF 进行互操作。”详情请看这里:https://publib.boulder.ibm.com/infocenter/ieduasst/v1r1m0/index.jsp?topic=/com.ibm.iea.wdatapower/wdatapower/1.0/xa35/380DataPowerWCFIntegration/player.html

如果您希望仅对消息正文进行签名(而不是加密),请确保您已在服务合同上设置 ProtectionLevel.Sign。

对于我之前遇到问题的 DNS 身份,我现在可以输入我的客户端证书主题名称 - 之前这不起作用。

我的 web.config 中没有任何配置。

这是使用 CustomBinding 的代理:

private ClientProxy GetProxy()

    XXXServiceClient proxy = new XXXServiceClient(GetCustomBinding(), new EndpointAddress(new Uri("<<GatewayURLHere>>"), EndpointIdentity.CreateDnsIdentity("<<DNS or Client Cert Subject Name>>"), new AddressHeaderCollection()));
    proxy.Endpoint.Behaviors.Remove(typeof(ClientCredentials));
    proxy.Endpoint.Behaviors.Add(new CustomCredentials(<clientCertHere>, <signingCertHere>));
    proxy.ClientCredentials.UserName.UserName = @"XXX";
    proxy.ClientCredentials.UserName.Password = "yyy";
    return proxy;


private Binding GetCustomBinding()

    TransportSecurityBindingElement secBE = SecurityBindingElement.CreateCertificateOverTransportBindingElement(MessageSecurityVersion.WSSecurity10WSTrustFebruary2005WSSecureConversationFebruary2005WSSecurityPolicy11BasicSecurityProfile10);
    secBE.EndpointSupportingTokenParameters.Signed.Add(new UserNameSecurityTokenParameters  InclusionMode = SecurityTokenInclusionMode.Never, RequireDerivedKeys = false );
    secBE.EnableUnsecuredResponse = true;
    secBE.IncludeTimestamp = true;
    TextMessageEncodingBindingElement textEncBE = new TextMessageEncodingBindingElement(MessageVersion.Soap11WSAddressingAugust2004, System.Text.Encoding.UTF8);
    HttpsTransportBindingElement httpsBE = new HttpsTransportBindingElement();
    httpsBE.RequireClientCertificate = true;

    CustomBinding myBinding = new CustomBinding();
    myBinding.Elements.Add(secBE);
    myBinding.Elements.Add(textEncBE);
    myBinding.Elements.Add(httpsBE);

    return myBinding;

这是我的 CustomCredentials 类,我从多个来源(包括上面提到的 UsernameToken 库)组合在一起 - 设置客户端证书以在传输层进行(相互?)身份验证,用于签署消息正文的服务/签名证书和带有密码摘要的 UsernameToken在 SOAP 标头中:

using System;
using System.IdentityModel.Selectors;
using System.IdentityModel.Tokens;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.ServiceModel.Security;
using System.Text;

namespace XXX_WCF

    public class CustomCredentials : ClientCredentials
    
        private X509Certificate2 clientAuthCert;
        private X509Certificate2 clientSigningCert;

        public CustomCredentials() : base()  

        public CustomCredentials(CustomCredentials other)
            : base(other)
        
            clientSigningCert = other.clientSigningCert;
            clientAuthCert = other.clientAuthCert;
        

        protected override ClientCredentials CloneCore()
        
            CustomCredentials scc = new CustomCredentials(this);
            return scc;
        

        public CustomCredentials(X509Certificate2 ClientAuthCert, X509Certificate2 ClientSigningCert)
            : base()
        
            clientAuthCert = ClientAuthCert;
            clientSigningCert = ClientSigningCert;
        

        public X509Certificate2 ClientAuthCert
        
            get  return clientAuthCert; 
            set  clientAuthCert = value; 
        

        public X509Certificate2 ClientSigningCert
        
            get  return clientSigningCert; 
            set  clientSigningCert = value; 
        

        public override SecurityTokenManager CreateSecurityTokenManager()
        
            return new CustomTokenManager(this);
        
    

    public class CustomTokenManager : ClientCredentialsSecurityTokenManager
    
        private CustomCredentials custCreds;

        public CustomTokenManager(CustomCredentials CustCreds)
            : base(CustCreds)
        
            custCreds = CustCreds;
        

        public override SecurityTokenProvider CreateSecurityTokenProvider(SecurityTokenRequirement tokenRequirement)
        
            if (tokenRequirement.TokenType == SecurityTokenTypes.X509Certificate)
            
                x509CustomSecurityTokenProvider prov;
                object temp = null;
                TransportSecurityBindingElement secBE = null;

                if (tokenRequirement.Properties.TryGetValue("http://schemas.microsoft.com/ws/2006/05/servicemodel/securitytokenrequirement/SecurityBindingElement", out temp))
                
                    secBE = (TransportSecurityBindingElement)temp;
                

                if (secBE == null)
                    prov = new x509CustomSecurityTokenProvider(custCreds.ClientAuthCert);
                else
                    prov = new x509CustomSecurityTokenProvider(custCreds.ClientSigningCert);
                return prov;
            

            return base.CreateSecurityTokenProvider(tokenRequirement);
        

        public override System.IdentityModel.Selectors.SecurityTokenSerializer CreateSecurityTokenSerializer(System.IdentityModel.Selectors.SecurityTokenVersion version)
        
            return new CustomTokenSerializer(System.ServiceModel.Security.SecurityVersion.WSSecurity10);
        
    

    class x509CustomSecurityTokenProvider : SecurityTokenProvider
    
        private X509Certificate2 clientCert;

        public x509CustomSecurityTokenProvider(X509Certificate2 cert)
            : base()
        
            clientCert = cert;
        

        protected override SecurityToken GetTokenCore(TimeSpan timeout)
        
            return new X509SecurityToken(clientCert);
        
    

    public class CustomTokenSerializer : WSSecurityTokenSerializer
    
        public CustomTokenSerializer(SecurityVersion sv) : base(sv)  

        protected override void WriteTokenCore(System.Xml.XmlWriter writer, System.IdentityModel.Tokens.SecurityToken token)
        
            if (writer == null)
            
                throw new ArgumentNullException("writer");
            
            if (token == null)
            
                throw new ArgumentNullException("token");
            

            if (token.GetType() == new UserNameSecurityToken("x", "y").GetType())
            
                UserNameSecurityToken userToken = token as UserNameSecurityToken;

                if (userToken == null)
                
                    throw new ArgumentNullException("userToken: " + token.ToString());
                

                string tokennamespace = "o";

                DateTime created = DateTime.Now;
                string createdStr = created.ToString("yyyy-MM-ddThh:mm:ss.fffZ");
                string phrase = Guid.NewGuid().ToString();
                string nonce = GetSHA1String(phrase);
                string password = GetSHA1String(nonce + createdStr + userToken.Password);
                //string password = userToken.Password;

                writer.WriteStartElement(tokennamespace, "UsernameToken", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd");
                writer.WriteAttributeString("u", "Id", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd", token.Id);
                writer.WriteElementString(tokennamespace, "Username", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd", userToken.UserName);
                writer.WriteStartElement(tokennamespace, "Password", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd");
                writer.WriteAttributeString("Type", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest");
                writer.WriteValue(password);
                writer.WriteEndElement();
                writer.WriteStartElement(tokennamespace, "Nonce", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd");
                writer.WriteAttributeString("EncodingType", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary");
                writer.WriteValue(nonce);
                writer.WriteEndElement();
                writer.WriteElementString(tokennamespace, "Created", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd", createdStr);
                writer.WriteEndElement();
                writer.Flush();
            
            else
            
                base.WriteTokenCore(writer, token);
            
        

        protected string GetSHA1String(string phrase)
        
            SHA1CryptoServiceProvider sha1Hasher = new SHA1CryptoServiceProvider();
            byte[] hashedDataBytes = sha1Hasher.ComputeHash(Encoding.UTF8.GetBytes(phrase));
            return Convert.ToBase64String(hashedDataBytes);
        
    //CustomTokenSerializer

祝你好运!

【讨论】:

以上是关于WCF SOAP 1.1 和 WS-Security 1.0、客户端证书传输身份验证、消息正文签名的服务证书、用户名令牌、密码摘要、随机数的主要内容,如果未能解决你的问题,请参考以下文章

使用 WCF 生成 SOAP 请求 (RSA-SHA256 PKCS #1 v1.5)

在现有 WCF SOAP 服务中添加 Http 标头不起作用

WCF 服务的 REST / SOAP 端点

如何在 wcf 中添加自定义肥皂标题?

您可以使用 SOAP 和 WSHttpBinding 对 WCF 服务进行 jQuery 调用吗?

将 WCF SOAP 和 WCF REST 服务托管为 Azure 应用服务