【问题标题】:How to make WCF Client conform to specific WS-Security - sign UsernameToken and SecurityTokenReference如何使 WCF 客户端符合特定的 WS-Security - 签署 UsernameToken 和 SecurityTokenReference
【发布时间】:2012-10-11 04:27:45
【问题描述】:

我需要创建一个 wcf 客户端来调用我无法控制的服务。

我得到了一个 wsdl 和一个可用的 soapui 项目。

该服务同时使用用户名/密码和 x509 证书。


更新

我现在了解问题所在,但仍不确定我需要采取哪些步骤才能创建所需的消息,因此非常感谢任何帮助。

我需要同时签署 UsernameToken 和 SecurityTokenReference。

我必须创建自定义绑定的代码已从该帖子中删除,因为它不再使用。我不再将 SecurityBindingElement 添加到绑定中,而是添加了一个将安全元素写入标头的新行为。

因此,安全节点是通过继承 SignedXml 类、添加签名引用然后调用 ComputeSignature 以在 Security 标头中创建 Signature 节点来从头开始创建的。

您需要将 xml 传递给 SignedXml 构造函数才能使其工作。传递 UsernameToken 没有问题,而且签名似乎正确。

问题是 SecurityTokenReference 仅在调用 ComputeSignature() 时创建,因此我无法为此元素添加签名引用,因为它在需要时不存在(在被覆盖的 GetIdElement在 ComputeSignature()) 之前调用的 SignedXml 方法


我用来创建要插入到安全标头中的签名块的代码如下

   string certificatePath = System.Windows.Forms.Application.StartupPath + "\\" + "Certs\\sign-and-    enc.p12";

    XmlDocument xd = new XmlDocument();
    xd.LoadXml(xml);

    // Set Certificate 
    System.Security.Cryptography.X509Certificates.X509Certificate2 cert = new X509Certificate2(certificatePath, "password");
    MySignedXml signedXml = new MySignedXml(xd);
    signedXml.SigningKey = cert.PrivateKey;

    // Create a new KeyInfo object. 
    KeyInfo keyInfo = new KeyInfo();
    keyInfo.Id = "";

    MemoryStream keyInfoStream = new MemoryStream();
    XmlWriter keyInfoWriter = XmlWriter.Create(keyInfoStream);

    WSSecurityTokenSerializer.DefaultInstance.WriteKeyIdentifierClause(keyInfoWriter, new LocalIdKeyIdentifierClause("token_reference", typeof(X509SecurityToken)));   

    keyInfoWriter.Flush();
    keyInfoStream.Position = 0;
    XmlDocument keyInfoDocument = new XmlDocument();
    keyInfoDocument.Load(keyInfoStream);

    XmlAttribute attrib = keyInfoDocument.CreateAttribute("ValueType");
    attrib.InnerText = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3";
    keyInfoDocument.ChildNodes[1].ChildNodes[0].Attributes.Append(attrib);

    KeyInfoNode keyInfoNode = new KeyInfoNode();
    keyInfoNode.LoadXml(keyInfoDocument.DocumentElement);
    keyInfo.AddClause(keyInfoNode);

    // Add the KeyInfo object to the SignedXml object. 
    signedXml.KeyInfo = keyInfo;

    // Need to use External Canonicalization method. 
    signedXml.SignedInfo.CanonicalizationMethod = "http://www.w3.org/2001/10/xml-exc-c14n#";

    // Create a reference to be signed. 
    Reference reference = new Reference();
    reference.Uri = "#UsernameToken-1";

    XmlDsigEnvelopedSignatureTransform env = new XmlDsigEnvelopedSignatureTransform();
    env.Algorithm = "http://www.w3.org/2001/10/xml-exc-c14n#";
    reference.AddTransform(env);
    reference.DigestMethod = "http://www.w3.org/2000/09/xmldsig#sha1";
    signedXml.AddReference(reference);


    Reference reference2 = new Reference();
    reference2.Uri = "#token_reference";
    XmlDsigEnvelopedSignatureTransform env2 = new XmlDsigEnvelopedSignatureTransform();
    env2.Algorithm = "http://www.w3.org/2001/10/xml-exc-c14n#";
    reference2.AddTransform(env2);
    reference2.DigestMethod = "http://www.w3.org/2000/09/xmldsig#sha1";
    signedXml.AddReference(reference2);

    // Add the Signature Id 
    signedXml.Signature.Id = "MYSIG_ID";

    // Compute the signature. 
    signedXml.ComputeSignature();

    XmlElement xmlDigitalSignature = signedXml.GetXml();

其中 xml 变量是 UsernameToken xml 字符串,而 MySignedXml 类是一个子类 SignedXml,其中 GetIdElement 方法被覆盖(以尝试找到并正确地重新释放不存在的 SecurityTokenReference)

我现在花了几天的时间研究和测试这个,不幸的是提供服务的公司没有任何帮助 - 但我需要使用他们的服务。

完整的工作肥皂消息(soapUI 项目)

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:urn="urn:XXXXX">
   <soapenv:Header xmlns:ebxml="http://docs.oasis-open.org/ebxml-msg/ebms/v3.0/ns/core/200704/">
   <wsse:Security soapenv:mustUnderstand="1" xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-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="CertId-D05E596B5ABC341FEB13505090224061" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">MIIEnDCCBAWgAwIBAgIBAjANBgkqhkiG9w0BAQUFADCBxDELMAkGA1UEBhMCTloxDTALBgNVBAgTBFdHVE4xEzARBgNVBAcTCldlbGxpbmd0b24xGDAWBgNVBAoTD0lSRFN0dWR5bGlua0IyQjEUMBIGA1UECxMLRGV2ZWxvcG1lbnQxHjAcBgNVBAMTFWRldmNhLmIyYi5pcmQuZ292dC5uejEXMBUGA1UEKRMORGV2ZWxvcG1lbnQgQ0ExKDAmBgkqhkiG9w0BCQEWGWNocmlzLnNjaHVsdHpAaXJkLmdvdnQubnowHhcNMTEwOTE1MDIwNjIwWhcNMjEwOTEyMDIwNjIwWjCByTELMAkGA1UEBhMCTloxDTALBgNVBAgTBFdHVE4xEzARBgNVBAcTCldlbGxpbmd0b24xGDAWBgNVBAoTD0lSRFN0dWR5bGlua0IyQjEUMBIGA1UECxMLRGV2ZWxvcG1lbnQxJTAjBgNVBAMTHHNpZ24tYW5kLWVuYy5kZXYuaXJkLmdvdnQubnoxFTATBgNVBCkTDHNpZ24tYW5kLWVuYzEoMCYGCSqGSIb3DQEJARYZY2hyaXMuc2NodWx0ekBpcmQuZ292dC5uejCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAykyZHVnXjsG220zB3kNOsGBeGP2rdNbLlIqW1D8yZO1fcj3/RhRiqsopbUrb8AU1ClpfhbH2K68kg7V91VAY0qrwNxP+pPPo1vYKMU6pT38qJGQzffr+iV2BCJshZvSk9E7QSWO5mFNstdg19xc+5ST1Lgb3fefuRG2KZVxPx0sCAwEAAaOCAZUwggGRMAkGA1UdEwQCMAAwEQYJYIZIAYb4QgEBBAQDAgZAMDQGCWCGSAGG+EIBDQQnFiVFYXN5LVJTQSBHZW5lcmF0ZWQgU2VydmVyIENlcnRpZmljYXRlMB0GA1UdDgQWBBSczRKXKPe3Sin7eFrVXfI7MXckzzCB+QYDVR0jBIHxMIHugBSLWxPSZd9otEj16vhLyovMCI9OMaGByqSBxzCBxDELMAkGA1UEBhMCTloxDTALBgNVBAgTBFdHVE4xEzARBgNVBAcTCldlbGxpbmd0b24xGDAWBgNVBAoTD0lSRFN0dWR5bGlua0IyQjEUMBIGA1UECxMLRGV2ZWxvcG1lbnQxHjAcBgNVBAMTFWRldmNhLmIyYi5pcmQuZ292dC5uejEXMBUGA1UEKRMORGV2ZWxvcG1lbnQgQ0ExKDAmBgkqhkiG9w0BCQEWGWNocmlzLnNjaHVsdHpAaXJkLmdvdnQubnqCCQDL/qDdlx2j6DATBgNVHSUEDDAKBggrBgEFBQcDATALBgNVHQ8EBAMCBaAwDQYJKoZIhvcNAQEFBQADgYEAS4ZPIVVpgTOGN4XcIC3SiYlxF8wYg7qnUhH5wJsAD3VCKfj68j9FSJtdBWLlWvvRxEoDP2lZ0IbFl6Rjnl+2ibzFnyac2G1AVm5mwPrNKHBQJ9J5eDJi0iUVY7Wphz86tKnqj34GvlHPNXmrF7oGEcDhPwK0T8zRdE/pvKIUiJc=</wsse:BinarySecurityToken>
    <ds:Signature Id="Signature-2" xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
        <ds:SignedInfo>
            <ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
            <ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
            <ds:Reference URI="#CertId-D05E596B5ABC341FEB13505090224061">
                <ds:Transforms>
                    <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
                </ds:Transforms>
                <ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
                <ds:DigestValue>3iVAUEAt8vAb7Ku+jf2gwSkSm0Q=</ds:DigestValue>
            </ds:Reference>
            <ds:Reference URI="#UsernameToken-1">
                <ds:Transforms>
                    <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
                </ds:Transforms>
                <ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
                <ds:DigestValue>r4HLEAWJldJwmEmcAqV6Y8rnTPE=</ds:DigestValue>
            </ds:Reference>
        </ds:SignedInfo>
        <ds:SignatureValue>
        YGh2I3VcukqjT0O7hKItiykWN5tlID18ZXRCwQjXriHmnVsO4wGcHjWfmhuNDecq+xRN+SjG8E7M
        2Rx/5/BbFKbVlNOkQOSbSxIs1YT9GaThK16pMrX5KRkkJme1W3R0pGIIQh6gGRSUf79RZUIYxyVl
        LqdIe561TXXDdtbt/6Q=
        </ds:SignatureValue>
        <ds:KeyInfo Id="KeyId-D05E596B5ABC341FEB13505090224372">
            <wsse:SecurityTokenReference wsu:Id="STRId-D05E596B5ABC341FEB13505090224373" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
                <wsse:Reference URI="#CertId-D05E596B5ABC341FEB13505090224061" ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3"/></wsse:SecurityTokenReference>
        </ds:KeyInfo>
    </ds:Signature>
    <wsse:UsernameToken wsu:Id="UsernameToken-1" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
        <wsse:Username>XXXXXX</wsse:Username>
        <wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">XXXXXXX</wsse:Password>
        </wsse:UsernameToken>
    </wsse:Security>
  <ebxml:Messaging xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
     <ebxml:UserMessage>
        <ebxml:MessageInfo>
           <ebxml:Timestamp>2002-02-02T14:18:02.0Z</ebxml:Timestamp>
           <ebxml:MessageId>bf9433d9-c6e9-4c12-9c98-724008a09c21</ebxml:MessageId>
        </ebxml:MessageInfo>
        <ebxml:PartyInfo>
           <ebxml:From>
              <ebxml:PartyId type="identifier">Trading Partner X</ebxml:PartyId>
              <ebxml:Role>Provider</ebxml:Role>
           </ebxml:From>
           <ebxml:To>
              <ebxml:PartyId type="identifier">XXXXXXX</ebxml:PartyId>
              <ebxml:Role>Requestor</ebxml:Role>
           </ebxml:To>
        </ebxml:PartyInfo>
        <ebxml:CollaborationInfo>
           <ebxml:AgreementRef>urn:XXXXXXXXX</ebxml:AgreementRef>
           <ebxml:Service type="Web Service">urn:XXXXXXXX</ebxml:Service>
           <ebxml:Action>customerInformation</ebxml:Action>
           <ebxml:ConversationId>e302426a-b2d9-4ff1-a14b-fbbc2f40a017</ebxml:ConversationId>
        </ebxml:CollaborationInfo>
     </ebxml:UserMessage>
  </ebxml:Messaging>
   </soapenv:Header>
   <soapenv:Body>
  <urn:ConnectionTest>
     <Message>Bonjour</Message>
  </urn:ConnectionTest>
   </soapenv:Body>
</soapenv:Envelope>

我们收到了一些文件。安全部分复制如下

必须按以下顺序对每个请求应用以下安全性: 必须包含 wsse:UsernameToken 并包含:

  1. 代理的门户用户名作为 wsse:Username 元素的值
  2. Password 元素中 wsse:PasswordText(明文)值的代理门户密码 签名块: 数字签名是使用 x509 证书创建的。每个软件提供商都需要持有和保护由[待定]颁发的有效证书和私钥。对于每个服务请求,软件必须:

  3. 包含与用于签名的私钥匹配的证书作为 wsse:BinarySecurityToken 并将其用作签名的 wsse:SecurityTokenReference

  4. 签署 wsse:BinarySecurityToken
  5. 签署 wsse:UsernameToken
  6. 使用规范化方法算法:http://www.w3.org/2001/10/xml-exc-c14n#
  7. 使用签名方法算法:http://www.w3.org/2000/09/xmldsig#rsa-sha1
  8. 使用摘要法算法:http://www.w3.org/2000/09/xmldsig#sha1

更新

最初给我的 SoapUI 配置的图像

【问题讨论】:

  • 我认为您需要做的第一件事是将一些客户端服务配置移动到配置文件中。我将在测试项目中试一试。他们提供证书了吗?
  • 这听起来有点奇怪。我看不到我曾经遇到过那种安全模型。通常,如果您使用用户名和密码进行消息身份验证,您将使用传输级安全性。如果您使用消息级安全性,则将使用指定证书,而不是用户名和密码。
  • 已经感谢您的 cmets,我正在为我的问题添加一些进一步的注释作为回应
  • 我想我正在走得更远,但仍未解决问题。在soapui 项目中,如果我从传出的ws-security 配置的签名部分中删除了令牌,那么我在Fiddler 中会遇到同样的错误(无法使用任何支持的令牌类型验证签名)。我现在的想法是我没有正确签署 wcf 消息中的这个“令牌”部分。根据我的阅读,我认为这个令牌实际上是 BinarySecurityToken 但是当我将我的消息与工作的 soapui 消息进行比较时,这个元素的值是相同的。有人可以在这里给我一些指示吗?某人
  • 请发布完整的soapui和c#请求消息

标签: c# wcf x509certificate ws-security


【解决方案1】:

今天终于解决了这个问题。在术语方面,我需要签名的不是 SecurityTokenReference,而是 Binary Security Token。

为了做到这一点,我需要隐藏发起者和接收者的证书并添加一个签名的支持令牌。

我回到使用配置来创建和签名消息,而不是尝试手动添加签名。

可能会阻止此工作的其他问题是,我的自定义“消息传递”标头上的命名空间不正确,因此请注意命名空间,我认为它们不会像它们现在一样重要。

创建绑定的代码如下

    private System.ServiceModel.Channels.Binding GetCustomBinding()
    {
        System.ServiceModel.Channels.AsymmetricSecurityBindingElement asbe = new AsymmetricSecurityBindingElement();
        asbe.MessageSecurityVersion = MessageSecurityVersion.WSSecurity11WSTrust13WSSecureConversation13WSSecurityPolicy12;

        asbe.InitiatorTokenParameters = new System.ServiceModel.Security.Tokens.X509SecurityTokenParameters { InclusionMode = SecurityTokenInclusionMode.Never };
        asbe.RecipientTokenParameters = new System.ServiceModel.Security.Tokens.X509SecurityTokenParameters { InclusionMode = SecurityTokenInclusionMode.Never };
        asbe.MessageProtectionOrder = System.ServiceModel.Security.MessageProtectionOrder.SignBeforeEncrypt;

        asbe.SecurityHeaderLayout = SecurityHeaderLayout.Strict;
        asbe.EnableUnsecuredResponse = true;
        asbe.IncludeTimestamp = false;
        asbe.SetKeyDerivation(false);
        asbe.DefaultAlgorithmSuite = System.ServiceModel.Security.SecurityAlgorithmSuite.Basic128Rsa15;
        asbe.EndpointSupportingTokenParameters.Signed.Add(new UserNameSecurityTokenParameters());
        asbe.EndpointSupportingTokenParameters.Signed.Add(new X509SecurityTokenParameters());

        CustomBinding myBinding = new CustomBinding();
        myBinding.Elements.Add(asbe);
        myBinding.Elements.Add(new TextMessageEncodingBindingElement(MessageVersion.Soap11, Encoding.UTF8));

        HttpsTransportBindingElement httpsBindingElement = new HttpsTransportBindingElement();
        httpsBindingElement.RequireClientCertificate = true;
        myBinding.Elements.Add(httpsBindingElement);

        return myBinding;
    }

使用绑定时,我设置了ClientCredentials UserName、ServiceCertificate和ClientCertificate,一切都按预期工作。


使用代码如下

using (CredentialingService.SOAPPortTypeClient client = GetCredentialingClient())
{
    client.Open();
    etc....
}

private static CredentialingService.SOAPPortTypeClient GetCredentialingClient()
{
    CredentialingService.SOAPPortTypeClient client = new CredentialingService.SOAPPortTypeClient(GetCustomBinding(), new EndpointAddress(new Uri(Settings.AppSettings.B2BUrl), new DnsEndpointIdentity(Settings.AppSettings.B2BDNSEndpoint), new AddressHeaderCollection()));
    client.Endpoint.Contract.ProtectionLevel = System.Net.Security.ProtectionLevel.None;
    SetClientCredentialsSecurity(client.ClientCredentials);

    return client;
}

在我的帖子中指定 GetCustomBinding 的位置

SetClientCredentialsSecurity是设置证书的地方,如下

private static void SetClientCredentialsSecurity(ClientCredentials clientCredentials)
{
    clientCredentials.UserName.UserName = Settings.AppSettings.B2BUserName;
    clientCredentials.UserName.Password = Settings.AppSettings.B2BPassword;

    string directoryName = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
    clientCredentials.ServiceCertificate.DefaultCertificate = new X509Certificate2(Path.Combine(directoryName, Settings.AppSettings.B2BServerCertificateName));
    clientCredentials.ClientCertificate.Certificate = new X509Certificate2(Path.Combine(directoryName, Settings.AppSettings.B2BClientCertificateName), Settings.AppSettings.B2BClientCertificatePassword);
}

希望这样可以更清楚一点

【讨论】:

  • 你在哪里指定证书来签署文件?
  • @Habeeb 我已经添加到我的帖子中,希望这会让一切变得更清晰
【解决方案2】:

可能是因为您的证书不是公开有效的。试试这个:

System.Net.ServicePointManager.ServerCertificateValidationCallback = new System.Net.Security.RemoteCertificateValidationCallback(delegate { return true; });
//...
using (var client = new SrvClient())
{
    client.ClientCredentials.UserName.UserName = "usr";
    client.ClientCredentials.UserName.Password = "psw";
    //...
}

【讨论】:

  • 感谢您的回复@Kaveh,但我不确定这与我现在正在做的事情有何不同(“使用”声明除外)-除非我遗漏了什么?我已经尝试了 Yaron 上面提到的大部分内容。硬编码一切都不起作用,但我认为现在一种方法可能是对标头进行硬编码,但 Signature 节点除外,然后尝试通过代码对需要签名的位进行签名。我会在接下来的几天里试试这个,我会发回我发现的任何东西。
  • 我没有看到你代码的第一行。尝试将该行添加到您的代码中,如果您的问题(我猜)是关于无效证书的,它将得到解决。
  • 我明白了,谢谢@Kaveh 我错过了你的观点。我的代码中确实有该行,但将其排除在我上面发布的 sn-p 之外。我可能应该包括它以使事情清楚。不幸的是,包含那行代码对我的问题没有影响。
猜你喜欢
  • 2012-06-05
  • 1970-01-01
  • 1970-01-01
  • 2012-03-11
  • 1970-01-01
  • 1970-01-01
  • 2019-09-24
  • 2011-03-07
  • 1970-01-01
相关资源
最近更新 更多