如何使WCF客户端符合特定的WS-Security - 签署UsernameToken和SecurityTokenReference

26

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

我已经拿到了wsdl和一个可用的SoapUI项目。

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


更新:

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

我需要对UsernameToken和SecurityTokenReference进行签名。

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

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

你需要将要签名的XML传递给SignedXml构造函数才能使其正常工作。传递UsernameToken没有问题,它似乎被正确签名了。

问题在于,当调用ComputeSignature()时才会创建SecurityTokenReference,因此我不能向此元素添加签名引用,因为在其所需的时间(在SignedXml的重写GetIdElement方法之前调用ComputeSignature())它不存在。


我用于创建插入到安全头中的签名块的代码如下:

   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)

我已经花了几天时间进行研究和测试,不幸的是,提供该服务的公司没有提供任何帮助,但我需要使用他们的服务。

完整的工作soap消息(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证书创建。每个软件提供商都需要持有和保护由[待确定]发放的有效证书和私钥。对于每个服务请求,软件必须:
    1. 包括与用于签名的私钥匹配的证书,作为wsse:BinarySecurityToken,并将其用作Signature的wsse:SecurityTokenReference。
    2. 签署wsse:BinarySecurityToken。
    3. 签署wsse:UsernameToken。
    4. 使用规范化方法算法:http://www.w3.org/2001/10/xml-exc-c14n#
    5. 使用签名方法算法:http://www.w3.org/2000/09/xmldsig#rsa-sha1
    6. 使用摘要方法算法:http://www.w3.org/2000/09/xmldsig#sha1

更新

我最初收到的SoapUI配置图像: SoapUIConfig


感谢您已经提供的评论,我正在根据问题添加一些进一步的注释作为回应。 - Steve B
@SteveB:你是如何将自定义的SOAP头部信息包含到你的消息中的?至于证书,你是如何在soapUI中进行配置的? - VoodooChild
嗨@VoodooChild。试图回忆我在这里做了什么。我想,从记忆中来看,我最初尝试使用ClientMessageInspector在BeforeSendRequest上注入头信息。 - Steve B
嗨@VoodooChild。试图回忆我在这里做了什么。我想,从记忆中来看,我最初尝试使用ClientMessageInspector在BeforeSendRequest上注入标头信息,但是我的消息总是被服务拒绝,我从未设法使其工作。最后,我只是在我的GetCustomBinding方法中使用配置设置(如下),这给了我所需的内容。如果您认为有帮助,我可以向您发送完整的代码?关于SoapUI,我得到了一个可用的项目,所以不认为我需要进行任何配置。我会尝试添加配置设置的截图。 - Steve B
@SteveB - 感谢您的回复!我在这里提供了更多有关我的问题的详细信息http://stackoverflow.com/q/32703632/247184 如果您有时间,请看一下。我已经在soapUI中设置了所需的wsse头,但我需要帮助从wcf客户端端设置绑定和配置。 - VoodooChild
显示剩余8条评论
2个回答

13

今天终于解决了问题。在术语上,我需要签署的不是SecurityTokenReference,而是Binary Security Token。

为了做到这一点,我需要隐藏Initiator和Recipient的证书,并添加已签名的支持令牌。

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

这个问题的另一个阻碍就是,在我的自定义“Messaging”头部有一个错误的命名空间,所以要注意命名空间,我没有想到它们会像现在这样重要。

创建绑定的代码如下:

    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
@Habeeb 我已经更新了我的帖子,希望能让一切变得更加清晰明了。 - Steve B

0
也许是因为您的证书不是公开有效的。请尝试以下操作:
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,但我不确定那与我目前所做的有什么真正的区别(除了“using”语句)——除非我错过了什么?我已经尝试了Yaron上面提到的大部分内容。硬编码的所有内容都没有起作用,但我现在认为一个方法可能是硬编码标题,除了签名节点之外,然后尝试通过代码签署需要签署的位。我会在接下来的几天里尝试一下,并发布我发现的任何东西。 - Steve B
我没有看到你代码的第一行。尝试将该行添加到你的代码中,如果你的问题 - 我猜测 - 是关于无效证书,那么它将被解决。 - Kaveh Shahbazian
我明白了,谢谢@Kaveh。我错过了你的观点。我在我的代码中有那一行,但是在我上面发布的片段中将其省略了。我可能应该把它包含进去以使事情更清楚。不幸的是,包含那行代码对我的问题没有影响。 - Steve B

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接