使用C#将签名的SOAP消息创建为字符串

时间:2014-12-08 21:13:20

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

我需要调用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>

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

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

因此,我尝试将此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,然后像我一样转换+ hash来计算摘要值?

非常感谢任何帮助。感谢。

1 个答案:

答案 0 :(得分:3)

  1. 对于DigestValue,您需要规范化字符串,如下所示:
  2.    <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;
        }
    
    }
    

    规范化之后,您现在可以计算Hash :(我的是一个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。
    2. <sp:Trust10> <wsp:Policy> <sp:MustSupportIssuedTokens/> <sp:RequireClientEntropy/> <sp:RequireServerEntropy/> </wsp:Policy>
      
           

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

      您可以使用具有KeyGenerator对象的Microsoft.IdentityModel dll将它们组合在一起。

      您的输入将是这样的,它还需要使用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 =