Warning: file_get_contents(/data/phpspider/zhask/data//catemap/7/wcf/4.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
具有WS-Security的WCF服务只需要签名的时间戳_Wcf_Ws Security - Fatal编程技术网

具有WS-Security的WCF服务只需要签名的时间戳

具有WS-Security的WCF服务只需要签名的时间戳,wcf,ws-security,Wcf,Ws Security,我需要向第三方提供服务,该第三方将发送带有签名时间戳的soap消息 如何配置我的服务以支持此功能 更新 我已经设法接近了我们所关注的Soap消息的格式,但是WCF坚持同时对用户名和时间戳令牌进行签名,有没有办法修改 绑定到只对时间戳签名 进一步更新 以下是我们的要求: 必须对Timestamp元素进行签名 用于签名的证书上的CN名称必须与UsernameToken元素中给定的用户名匹配 用于签名的证书必须在BinarySecurityToken元素中发送 KeyInfo元素只能包含Secur

我需要向第三方提供服务,该第三方将发送带有签名时间戳的soap消息

如何配置我的服务以支持此功能

更新 我已经设法接近了我们所关注的Soap消息的格式,但是WCF坚持同时对用户名和时间戳令牌进行签名,有没有办法修改 绑定到只对时间戳签名


进一步更新 以下是我们的要求:

  • 必须对Timestamp元素进行签名
  • 用于签名的证书上的CN名称必须与UsernameToken元素中给定的用户名匹配
  • 用于签名的证书必须在BinarySecurityToken元素中发送
  • KeyInfo元素只能包含SecurityTokenReference元素,该元素必须用于引用BinarySecurityToken
  • 必须指定规范化算法
  • 必须指定SignatureMethod,并且必须是SHA-1或SHA-2算法
  • 应使用分离的签名

有什么建议吗

当前配置

客户端绑定

<bindings>
  <wsHttpBinding>
    <binding name="WSBC">
      <security mode="TransportWithMessageCredential">
        <transport clientCredentialType="Certificate" proxyCredentialType="None"></transport>
        <message clientCredentialType="UserName" negotiateServiceCredential="false" establishSecurityContext="false" />
      </security>
    </binding>
  </wsHttpBinding>
</bindings>

客户端端点

<client>
  <endpoint address="https://localhost/WcfTestService/Service2.svc"
  behaviorConfiguration="CCB" binding="wsHttpBinding"
  bindingConfiguration="WSBC"
  contract="ServiceReference2.IService2"
  name="wsHttpBinding_IService2" />
</client>

客户行为

<behaviors>
  <endpointBehaviors>
    <behavior name="MBB">
      <clientCredentials>
        <clientCertificate  findValue="03 58 d3 bf 4b e7 67 2e 57 05 47 dc e6 3b 52 7f f8 66 d5 2a"
                            storeLocation="LocalMachine"
                            storeName="My"
                            x509FindType="FindByThumbprint" />
        <serviceCertificate>
          <defaultCertificate findValue="03 58 d3 bf 4b e7 67 2e 57 05 47 dc e6 3b 52 7f f8 66 d5 2a"
                              storeLocation="LocalMachine"
                              storeName="My"
                              x509FindType="FindByThumbprint"  />
        </serviceCertificate>
      </clientCredentials>
    </behavior>
  </endpointBehaviors>
</behaviors>

服务绑定

<bindings>
  <wsHttpBinding>
    <binding name="ICB">
      <security mode="TransportWithMessageCredential">
        <transport clientCredentialType="Certificate" proxyCredentialType="None"></transport>
        <message    clientCredentialType="UserName" 
                    negotiateServiceCredential="false"
                    establishSecurityContext="false" />
      </security>
    </binding>
  </wsHttpBinding>
</bindings>

服务端点

<service name="WcfTestService.Service2" behaviorConfiguration="SCB">
    <endpoint     address="" binding="wsHttpBinding" contract="WcfTestService.IService2"
    bindingConfiguration="ICB" name="MS" />
</service>

服务行为

<behaviors>
  <serviceBehaviors>
    <behavior name="SCB">
      <serviceCredentials>
        <serviceCertificate     findValue="4d a9 d8 f2 fb 4e 74 bd a7 36 d7 20 a8 51 e2 e6 ea 7d 30 08"
                                storeLocation="LocalMachine"
                                storeName="TrustedPeople"   
                                x509FindType="FindByThumbprint" />
        <userNameAuthentication 
            userNamePasswordValidationMode="Custom" 
            customUserNamePasswordValidatorType="WcfTestService.UsernameValidator, WcfTestService" />
        <clientCertificate>
          <authentication certificateValidationMode="None" revocationMode="NoCheck" />
        </clientCertificate>
      </serviceCredentials>
      <serviceMetadata httpGetEnabled="true" />
      <serviceDebug includeExceptionDetailInFaults="false" />
    </behavior>
  </serviceBehaviors>
</behaviors>


< /代码> 您可能想考虑一个自定义安全绑定类,它实现了您想要的安全性,而不是WCF默认值。 这些MSDN链接解释了自定义绑定和SecurityBindingElement抽象基类:


您可以通过邮件合同来实现这一点,请参阅:

以下是上述链接中的一个示例:

[MessageContract]
public class PatientRecord 
{
   [MessageHeader(ProtectionLevel=None)] public int recordID;
   [MessageHeader(ProtectionLevel=Sign)] public string patientName;
   [MessageHeader(ProtectionLevel=EncryptAndSign)] public string SSN;
   [MessageBodyMember(ProtectionLevel=None)] public string comments;
   [MessageBodyMember(ProtectionLevel=Sign)] public string diagnosis;
   [MessageBodyMember(ProtectionLevel=EncryptAndSign)] public string medicalHistory;
}

注意保护级别None、Sign、EncryptAndSign

WCF本机不允许对时间戳进行签名,但不允许对用户名进行签名。首先,我很确定这与您面临的问题无关——服务器应该能够处理这两种情况。如果您确实需要用户名,那么我建议在安全性中完全不要使用用户名(例如“AnonymousForceCertificate”的安全模式),然后实现自定义消息编码器,手动将用户名/密码标记推送到正确位置的头中(注意不要更改消息中的任何签名部分,主要是时间戳).

像这样的问题有很多,但是没有一个有明确的答案,所以在花了很多时间之后,我把我的答案留给这个8岁的问题,希望它能帮助别人

我必须向一个黑匣子服务器发送一条带有密码摘要和签名时间戳(仅对时间戳进行签名)的SOAP消息,我认为这是Axis2。我用不同的安全配置和SignedXml类的派生变体蒙混过关,成功地使我的消息看起来有些正确,但始终无法生成有效的签名。根据微软的说法,WCF不像非WCF服务器那样规范化,WCF省略了一些名称空间,并以不同的方式重命名名称空间前缀,因此我永远无法让签名匹配

因此,经过大量的尝试和错误,以下是我自己动手做的方式:

  • 定义负责创建整个安全标头的自定义MessageHeader
  • 定义自定义MessageInspector以重命名名称空间,添加缺少的名称空间,并将我的自定义安全标头添加到请求标头
  • 下面是我需要生成的请求的一个示例:

    <soapenv:Envelope xmlns:ns1="http://somewebsite.com/" xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="https://anotherwebsite.com/xsd">
    <soapenv:Header>
        <wsse:Security 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:UsernameToken wsu:Id="UsernameToken-1">
                <wsse:Username>username</wsse:Username>
                <wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest">aABCDiUsrOy8ScJkdABCD/ZABCD=</wsse:Password>
                <wsse:Nonce EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary">ABCDxZ8IABCDg/pTK6E0Q==</wsse:Nonce>
                <wsu:Created>2019-03-07T21:31:00.281Z</wsu:Created>
            </wsse:UsernameToken>
            <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-1">...</wsse:BinarySecurityToken>
            <wsu:Timestamp wsu:Id="TS-1">
                <wsu:Created>2019-03-07T21:31:00Z</wsu:Created>
                <wsu:Expires>2019-03-07T21:31:05Z</wsu:Expires>
            </wsu:Timestamp>
            <ds:Signature Id="SIG-1" 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="ns1 soapenv xsd" 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-1">
                        <ds:Transforms>
                            <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#">
                                <ec:InclusiveNamespaces PrefixList="wsse ns1 soapenv xsd" 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>ABCDmhUOmjhBRPabcdB1wni53mabcdOzRMo3ABCDVbw=</ds:DigestValue>
                    </ds:Reference>
                </ds:SignedInfo>
                <ds:SignatureValue>...</ds:SignatureValue>
                <ds:KeyInfo Id="KI-1">
                    <wsse:SecurityTokenReference wsu:Id="STR-1">
                        <wsse:Reference URI="#X509-1" 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:Security>
    </soapenv:Header>
    <soapenv:Body>
        ...
    </soapenv:Body>
    
    下面是我的邮件检查器的代码:

    public class MessageInspector : IClientMessageInspector
    {
        // Data to be used to create the security header
        public HeaderData HeaderData { get; set; }
    
        public void AfterReceiveReply(ref System.ServiceModel.Channels.Message reply, object correlationState)
        {
            var lastResponseXML = reply.ToString(); // Not necessary but useful for debugging if you want to see the response.
        }
    
        public object BeforeSendRequest(ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel)
        {
            // This might not be necessary for your case but I remove a bunch of unnecessary WCF-created headers from the request.
            List<string> removeHeaders = new List<string>() { "Action", "VsDebuggerCausalityData", "ActivityId" };
            for (int h = request.Headers.Count() - 1; h >= 0; h--)
            {
                if (removeHeaders.Contains(request.Headers[h].Name))
                {
                    request.Headers.RemoveAt(h);
                }
            }
    
            // Make changes to the request.
            // For this case I'm adding/renaming namespaces in the header.
            var container = XElement.Parse(request.ToString()); // Parse request into XElement
            // Change "s" namespace to "soapenv"
            container.Add(new XAttribute(XNamespace.Xmlns + "soapenv", "http://schemas.xmlsoap.org/soap/envelope/"));
            container.Attributes().Where(a => a.Name.LocalName == "s").Remove();
            // Add other missing namespace
            container.Add(new XAttribute(XNamespace.Xmlns + "ns1", "http://somewebsite.com/"));
            container.Add(new XAttribute(XNamespace.Xmlns + "xsd", "http://anotherwebsite.com/xsd"));
            requestXml = container.ToString();
    
            // Create a new message out of the updated request.
            var ms = new MemoryStream();
            var sr = new StreamWriter(ms);
            var writer = new StreamWriter(ms);
            writer.Write(requestXml);
            writer.Flush();
            ms.Position = 0;
    
            var reader = XmlReader.Create(ms);
            request = Message.CreateMessage(reader, int.MaxValue, request.Version);
    
            // Add my custom security header
            // This is responsible for writing the security headers to the message
            CustomSecurityHeader header = new CustomSecurityHeader();
            // Pass data required to build security header
            header.HeaderData = new HeaderData()
            {
                Certificate = this.HeaderData.Certificate,
                Username = this.HeaderData.Username,
                Password = this.HeaderData.Password
                // ... Whatever else might be needed
            };
    
            // Add custom header to request headers
            request.Headers.Add(header);
    
            return request;
        }
    }
    
    创建安全标头XML

    这有点难看,但我最终做的是为安全标头的规范化部分创建XML模板,填充值,对SignedInfo部分进行适当的哈希和签名,然后将这些部分组合成完整的安全标头。我更愿意用代码来构建它们,但XmlDocument不会维护我添加的属性的顺序,这会弄乱我的规范化XML和签名,所以我保持简单

    为了确保我的部分被正确规范化,我使用了一个名为SC14N的工具。我输入了一个示例XML请求和一个对我想要规范化的部分的引用,以及任何包含的名称空间,它返回了相应的XML。我将它返回的XML保存到一个模板中,将值和ID替换为可以稍后替换的标记。我为Timestamp部分创建了一个模板,为SignedInfo部分创建了一个模板,为整个Security header部分创建了一个模板

    间距当然很重要,因此请确保xml保持未格式化状态,如果要加载XmlDocument,最好确保将PreserveWhitespace设置为true:

    XmlDocument doc = new XmlDocument() { PreserveWhitespace = true;}
    
    现在我的模板保存在参考资料中,当我需要对时间戳签名时,我将时间戳模板加载到字符串中,用正确的时间戳ID替换标记,创建并过期字段,因此我有类似的内容(使用正确的名称空间,当然没有换行符):

    接下来,我需要我的SignedInfo部分的模板。我从我的资源中提取它,并替换相应的标记(在我的例子中,是上面计算的timestamp引用ID和timestamp digestValue),然后得到SignedInfo部分的哈希:

    // Get hash of the signed info
    SHA256Managed shHash = new SHA256Managed();
    fileBytes = System.Text.Encoding.UTF8.GetBytes(signedInfoXmlString);
    hashBytes = shHash.ComputeHash(fileBytes);
    var signedInfoHashValue = Convert.ToBase64String(hashBytes);
    
    然后,我对签名信息的哈希进行签名以获得签名:

    using (var rsa = MyX509Certificate.GetRSAPrivateKey())
    {
        var signatureBytes = rsa.SignHash(hashBytes, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
        SignatureValue = Convert.ToBase64String(signatureBytes); // This is my signature!
    }
    
    如果失败,请确保您的证书设置正确,它还应具有私钥。如果您运行的是较旧版本的框架,那么您可能需要跨越一些障碍才能获得RSA密钥。看

    当前用户名密码摘要

    我没必要这么做
    XmlDocument doc = new XmlDocument() { PreserveWhitespace = true;}
    
    <wsu:Timestamp xmlns:ns1="..." xmlns:soapenv="..." xmlns:wsse=".." xmlns:wsu=".." wsu:Id="TI-3">
        <wsu:Created>2019-05-07T21:31:00Z</wsu:Created>
        <wsu:Expires>2019-05-07T21:36:00Z</wsu:Expires>
    </wsu:Timestamp>
    
    // Get hash of timestamp.
    SHA256Managed shHash = new SHA256Managed();
    var fileBytes = System.Text.Encoding.UTF8.GetBytes(timestampXmlString);
    var hashBytes = shHash.ComputeHash(fileBytes);
    var digestValue = Convert.ToBase64String(hashBytes);
    
    // Get hash of the signed info
    SHA256Managed shHash = new SHA256Managed();
    fileBytes = System.Text.Encoding.UTF8.GetBytes(signedInfoXmlString);
    hashBytes = shHash.ComputeHash(fileBytes);
    var signedInfoHashValue = Convert.ToBase64String(hashBytes);
    
    using (var rsa = MyX509Certificate.GetRSAPrivateKey())
    {
        var signatureBytes = rsa.SignHash(hashBytes, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
        SignatureValue = Convert.ToBase64String(signatureBytes); // This is my signature!
    }
    
        // Create nonce
        SHA1CryptoServiceProvider sha1Hasher = new SHA1CryptoServiceProvider();
        var nonce = Guid.NewGuid().ToString("N");
        var nonceHash = sha1Hasher.ComputeHash(Encoding.UTF8.GetBytes(nonce));
        var NonceValue = Convert.ToBase64String(nonceHash);
    
        var NonceCreatedTime = DateTimeOffset.UtcNow.ToString("yyyy-MM-ddThh:mm:ss.fffZ");
    
        // Create password digest Base64( SHA1(Nonce + Created + Password) )
        var nonceBytes = Convert.FromBase64String(NonceValue); // Important - convert from Base64
        var createdBytes = Encoding.UTF8.GetBytes(NonceCreatedTime);
        var passwordBytes = Encoding.UTF8.GetBytes(Password);
    
        var concatBytes = new byte[nonceBytes.Length + createdBytes.Length + passwordBytes.Length];
        System.Buffer.BlockCopy(nonceBytes, 0, concatBytes, 0, nonceBytes.Length);
        System.Buffer.BlockCopy(createdBytes, 0, concatBytes, nonceBytes.Length, createdBytes.Length);
        System.Buffer.BlockCopy(passwordBytes, 0, concatBytes, nonceBytes.Length + createdBytes.Length, passwordBytes.Length);
    
        // Hash the combined buffer
        var hashedConcatBytes = sha1Hasher.ComputeHash(concatBytes);
        var PasswordDigest = Convert.ToBase64String(hashedConcatBytes);
    
    var bstValue = Convert.ToBase64String(myCertificate.Export(X509ContentType.Cert));