Creating a signed SOAP message as a string from C #

I need to make a call to a web service I have to send with a soap request like this using C #. SoapBody and TimeStamp must be signed.

 <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>

      

I created this soap request and got a good response using WCF Client CustomBinding and a .pfx file with a private key certificate.

Most of the soap message signing examples use a certificate from a certificate store or a pfx file. But in my scenario, the user certificate (with the private key) stored on the smart card cannot export the private key of the certificate. So in this case, I cannot use the WCF CustomBinding, or I cannot use the SignedXml class to sign the SOAP message, because when I try to get the certificate programmatically, the private key is missing. (Also I got the private key using the NCryptoki-PKCS wrapper, but this type of private key is different from RSA, which I can't set for the WCF client or SignedXmlClass private key.)

So I am trying to create this SOAP message as a string and manually create DigestValues, BinarySecurityToken, and SignatureValue using a smart card.

I can calculate the BinarySecurityToken value as:

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

      

Also I have some code to calculate the digest value as:

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

      

  • I'm not sure if I calculated the digest values ​​correctly or not?

  • In order to compute the SignatureValue I think I need to get the hash of the SignedInfo part. I have a method that signs content (byte array) with a smart card. So how can I send SignedInfo content to this method? I mean, is it enough to get the hash of the SignedInfo block as a string? OR I got SignedInfo as XmlElement, then converted + hash as I did to calculate digest values?

Any help would be much appreciated. Thank.

+3


source to share


1 answer


  • For DigestValue, you need to canonicalize the string like this:
   <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>

      

So, you can just put this line as a parameter here:

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;
    }

}

      

After canonicalization, you can now compute the hash: (my SHA1 example). So let's put the return value of the above method on the parameter of this one. get something like 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. The meaning of the signature is complex and I can only cover a specific example. Check your WSDL service if it has a policy that looks something like this.
<sp:Trust10> <wsp:Policy> <sp:MustSupportIssuedTokens/> <sp:RequireClientEntropy/> <sp:RequireServerEntropy/> </wsp:Policy>

      

This means that you need to combine the client entropy (which is your key - any string based on which 64 is sent to the server to request a token) and Server Entropy (base 64 return key)



You can concatenate them using the Microsoft.IdentityModel dll where there is a KeyGenerator object.

Your input will be something like this and it also needs canonicalization with 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>

      

Here is the canonization:

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;
    }

}

      

Then this is how you get the signature value:

        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;
        }
    }

      

This will give you something like this. kykmlowWIW4TXRcCi46OfZPUBKQ =

+1


source







All Articles