【问题标题】:Creating Signed SOAP Message as a String with C#使用 C# 将签名的 SOAP 消息创建为字符串
【发布时间】:2014-12-08 21:13:20
【问题描述】:

我需要调用 Web 服务,我必须使用 C# 在下面发送这样的肥皂请求。 SoapBody 和 TimeStamp 必须签名。

 <soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:web="http://xyzt.com/">
   <soap: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: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#X509PKIPathv1" wsu:Id="X509-F4AF9673207AC5E0B614180667985061">MIIFsDCCBawwggSUoAMCAQICBgCaWhnEajANBgkqhkiG9w0BAQsFADBcMQswCQYDVQQGEwJUUjFNMEsGA1UEAwxETWFsaSBNw7xow7xyIEVsZWt0cm9uaWsgU2VydGlmaWthIEhpem1ldCBTYcSfbGF5xLFjxLFzxLEgLSBTw7xyw7xtIDEwHhcNMT</wsse:BinarySecurityToken>
         <ds:Signature Id="SIG-3" 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="soap web" 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="#id-2">
                  <ds:Transforms>
                     <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#">
                        <ec:InclusiveNamespaces PrefixList="web" xmlns:ec="http://www.w3.org/2001/10/xml-exc-c14n#"/>
                     </ds:Transform>
                  </ds:Transforms>
                  <ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
                  <ds:DigestValue>IZVrIpPCxiPcvyVOVv/d4nRPZWM=</ds:DigestValue>
               </ds:Reference>
               <ds:Reference URI="#TS-1">
                  <ds:Transforms>
                     <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#">
                        <ec:InclusiveNamespaces PrefixList="wsse soap web" xmlns:ec="http://www.w3.org/2001/10/xml-exc-c14n#"/>
                     </ds:Transform>
                  </ds:Transforms>
                  <ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
                  <ds:DigestValue>fltghgDztDtuVQX7y4t0ZJxAnxE=</ds:DigestValue>
               </ds:Reference>
            </ds:SignedInfo>
            <ds:SignatureValue>IOVXxBTp053aNJMbQj+VTiBblZ63peyJ1vWazKmEWNxN7RaeFfKELoxede8xQEqzSaB/u8exC7LLGYiEdChboVCf9liLMN4MmNj5JR6gfDrsL3azThf5hxLQ+WIE20PRoU6ozpp20zC1IaO3IU4ZaRLw</ds:SignatureValue>
            <ds:KeyInfo Id="KI-F4AF9673207AC5E0B614180667986422">
               <wsse:SecurityTokenReference wsse11:TokenType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509PKIPathv1" wsu:Id="STR-F4AF9673207AC5E0B614180667986643" xmlns:wsse11="http://docs.oasis-open.org/wss/oasis-wss-wssecurity-secext-1.1.xsd">
                  <wsse:Reference URI="#X509-F4AF9673207AC5E0B614180667985061" ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509PKIPathv1"/>
               </wsse:SecurityTokenReference>
            </ds:KeyInfo>
         </ds:Signature>
         <wsu:Timestamp wsu:Id="TS-1">
            <wsu:Created>2014-12-08T21:26:36.191Z</wsu:Created>
            <wsu:Expires>2014-12-08T21:36:36.191Z</wsu:Expires>
         </wsu:Timestamp>
      </wsse:Security>
   </soap:Header>
   <soap:Body wsu:Id="id-2" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
      <web:getStatus>
         <itemID>1234567</itemID>
      </web:getStatus>
   </soap:Body>
</soap:Envelope>

我通过使用 WCF 客户端 CustomBinding 和具有私钥证书的 .pfx 文件创建了这个肥皂请求并获得了良好的响应。

大多数关于签署soap消息的示例都使用来自证书存储或pfx文件的证书。但在我的场景中,用户的证书(带有私钥)存储在智能卡上,该证书私钥无法导出。所以在这种情况下,我不能使用 WCF CustomBinding 或者我不能使用 SignedXml 类来签署 SOAP 消息,因为当我尝试以编程方式获取证书时,私钥会丢失。 (另外我通过使用 NCryptoki - PKCS 包装器获得了私钥,但这种私钥类型与我无法为 WCF 客户端或 SignedXmlClass 私钥设置的 RSA 不同。)

所以,我尝试将此 SOAP 消息创建为字符串,并使用智能卡手动创建 DigestValues、BinarySecurityToken 和 SignatureValue。

我可以将 BinarySecurityToken 值计算为:

var certificate = GetX5092Certificate(); // X5092 certificate on smart card without private key
string binarySecToken= Convert.ToBase64String(certificate.RawData);

我还有一些代码来计算摘要值:

byte[] dataToHashTS = Encoding.UTF8.GetBytes(TimeStampReference.OuterXml);
XmlDsigExcC14NTransform transformDataTS = new XmlDsigExcC14NTransform("wsse soap web");
transformDataTS.LoadInput(new MemoryStream(dataToHashTS));
byte[] bDigestDataTS = transformDataTS.GetDigestedOutput(SHA1Managed.Create());
string sDigestDataTS = Convert.ToBase64String(bDigestDataTS); //timestamp digest
  • 我不确定我是否正确计算了摘要值?

  • 要计算 SignatureValue,我想我需要获取 SignedInfo 部分的哈希值。我有使用智能卡对内容(字节数组)进行签名的方法。那么如何将 SignedInfo 内容发送到此方法?我的意思是获得 SignedInfo 块的哈希作为字符串就足够了吗?或者我已经将 SignedInfo 作为 XmlElement,然后像计算摘要值一样转换 + 哈希?

任何帮助将不胜感激。谢谢。

【问题讨论】:

  • 请使用经常使用的标签,而不是使用较少的标签。我已将signature 替换为cryptography。请注意,无论如何它应该是digital-signature。点赞,不知道直接回答。
  • 我认为这个问题现在很可能已经解决了,但是对于你的第二个问题,它基本上取决于你的接收者如何验证你的签名。大多数签名/验证实现都使用hash(signed(msg)) 作为验证功能的输入。所以,在你的情况下,我建议实现 signedInfo 块的哈希。

标签: c# web-services soap cryptography ws-security


【解决方案1】:
  1. 对于您需要的 DigestValue,您需要将这样的字符串规范化:
   <u:Timestamp u:Id="_0" xmlns:u="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
              <u:Created>2016-06-14T22:56:10.896Z</u:Created>
              <u:Expires>2016-06-14T23:01:10.896Z</u:Expires>
          </u:Timestamp>

所以你可以把那个字符串作为参数放在这里:

private string CanonicalizeDsig(string input)
{
    XmlDocument doc = new XmlDocument();
    doc.PreserveWhitespace = false;
    try
    {
        doc.LoadXml(input);
        XmlDsigC14NTransform trans = new XmlDsigC14NTransform();
        trans.LoadInput(doc);
        String c14NInput = new StreamReader((Stream)trans.GetOutput(typeof(Stream))).ReadToEnd();

        return c14NInput;


    }
    catch (Exception ex)
    {
        return String.Empty;
    }

}

规范化后,您现在可以计算哈希:(我的是一个 SHA1 示例)。所以把上面方法的返回值放在这个的参数上。得到类似 JCMdwz5g8iq05Lj6tjfDOxKqT4k=

private string ComputeHashSHA1(string input)
{
    try
    {
        SHA1CryptoServiceProvider sha1Hasher = new SHA1CryptoServiceProvider();
        byte[] hashedDataBytes = sha1Hasher.ComputeHash(Encoding.UTF8.GetBytes(input));
        string digestValue = Convert.ToBase64String(hashedDataBytes);
        return digestValue;

    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message);
        return String.Empty;
    }

}
  1. 签名值是一个棘手的值,我只能介绍一个具体示例。 如果有类似这样的策略,请检查服务的 WSDL。
<sp:Trust10> <wsp:Policy> <sp:MustSupportIssuedTokens/> <sp:RequireClientEntropy/> <sp:RequireServerEntropy/> </wsp:Policy>

这意味着您需要结合客户端熵(这是您的密钥 - 您在获取令牌请求时发送到服务器的任何基于 64 的字符串)和服务器熵(返回基数 64 密钥)

您可以使用 Microsoft.IdentityModel dll 组合它们,其中有一个 KeyGenerator 对象。

您的输入将是这样的,它还需要使用 DsigExcC14N 进行规范化:

              <SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
              <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
              <SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#hmac-sha1"/>
              <Reference URI="#_0">
                  <Transforms>
                      <Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
                  </Transforms>
                  <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
                  <DigestValue>JCMdwz5g8iq05Lj6tjfDOxKqT4k=</DigestValue>
              </Reference>
          </SignedInfo>

这是规范化:

private string CanonicalizeExc(string input)
{
    XmlDocument doc = new XmlDocument();
    doc.PreserveWhitespace = false;
    try
    {
        doc.LoadXml(input);
        XmlDsigExcC14NTransform trans = new XmlDsigExcC14NTransform();
        trans.LoadInput(doc);
        String c14NInput = new StreamReader((Stream)trans.GetOutput(typeof(Stream))).ReadToEnd();

        return c14NInput;


    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.ToString());
        return String.Empty;
    }

}

那么这里是你如何获得签名值:

        private string ComputeHMACSHA1_PSHA(string input, string serversecret, string clientsecret)
    {
        try
        {
            byte[] signedInfoBytes = Encoding.UTF8.GetBytes(input);

            byte[] binarySecretBytesServer = Convert.FromBase64String(serversecret);
            byte[] binarySecretBytesClient = Convert.FromBase64String(clientsecret);

            byte[] key = KeyGenerator.ComputeCombinedKey(binarySecretBytesClient, binarySecretBytesServer, 256);


            HMACSHA1 hmac = new HMACSHA1(key);
            hmac.Initialize();

            byte[] hmacHash = hmac.ComputeHash(signedInfoBytes);
            string signatureValue = Convert.ToBase64String(hmacHash);
            return signatureValue;
        }
        catch (Exception ex)
        {
            return string.Empty;
        }
    }

它会给你这样的东西。 kykmlowWIW4TXRcCi46OfZPUBKQ=

【讨论】:

  • 您需要将 DigestValue 基于参考。我认为您需要使用 Body 和 Timestamp 来获取摘要值。
  • 这个答案(由 cyrus sembrano 提供)让我找到了解决我遇到的类似问题的方法。我花了至少 2 周和许多小时阅读大师们的无数问题、答案、博客文章等,但没有那么大师,一切都无济于事!因此,对于遇到此问题的任何其他人,不要仅仅因为用户有代表就打折这个答案。 1分!!
  • 我不得不使用 XmlDsigExcC14NTransform,因为 xml 表示“w3.org/2001/10/xml-exc-c14n#”和一个 SHA256 哈希,所以使用了 SHA256CryptoServiceProvider。所以只有改变那部分才能奏效。非常好!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-11-15
  • 1970-01-01
  • 1970-01-01
  • 2019-05-11
  • 1970-01-01
  • 2016-07-29
  • 1970-01-01
相关资源
最近更新 更多