Domanda

I am working on sending an XML message to a government agency (using that government agency's specifications, so I have no control over what the resulting XML needs to look like), and I am using C# for my development (company policy).

Two people that are far better than myself with C# and internet technologies reviewed the XML before I did, and informed me that WCF would not support the methods that were required to generate the signature on the XML document (this was a bit of a relief, since I haven't developed any WCF projects, and frightening, since I understand WCF be a mature web technology).

So, I ended up using a combination of LINQ to XML and System.Xml to generate the message, and attempt to sign it.

Here's a bit of a trimmed down sample of the XML:

<soapenv:Envelope 
  xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" 
  xmlns:soap-sec="http://schemas.xmlsoap.org/security/2000-12" 
  xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" 
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
  xmlns:sp="http://schemas.xmlsoap.org/ws/2005/07/securitypolicy" 
  xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" 
  xmlns:xs="http://www.w3.org/2001/XMLSchema" 
  xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" 
  xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy"
  xmlns:cns="http://customNamespace1.com" 
  xmlns:cnt="http://customNamespace2.com" 
  >
  <soapenv:Header>
    <ns2:Element1 xmlns:ns2="http://namespace2.element1.com" wsu:Id="id-1">
      ...
    </ns2:Element1>
    <ns2:Element2 xmlns:ns2="http://namespace2.element2.com/" wsu:Id="id-2">
      ...
    </ns2:Element2>
    <wsse:Security SOAP-ENV:mustUnderstand="1" xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
      <wsu:Timestamp wsu:Id="id-3">
        ...
      </wsu:Timestamp>
      <wsse:UsernameToken wsu:Id="id-4">
        ...
      </wsse:UsernameToken>
      <wsse:BinarySecurityToken ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3" EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="SecurityToken-5bf699c7-5336-4695-b395-88d2b984fe54" xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
        ...
      </wsse:BinarySecurityToken>
      <ds:Signature Id="SIG-6" 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-ENV cnt soap-sec soapenv sp cns wsdl wsp wsse wsu xs xsi" xmlns:ec="http://www.w3.org/2001/10/xml-exc-c14n#" />
          </ds:CanonicalizationMethod>
          <ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1" />
          <ds:Reference URI="#id-1">
            <ds:Transforms>
              <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#">
                <ec:InclusiveNamespaces PrefixList="SOAP-ENV cnt soap-sec soapenv sp cns wsdl wsp wsse wsu xs xsi" 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>...</ds:DigestValue>
          </ds:Reference>
          <ds:Reference URI="#id-2">
            <ds:Transforms>
              <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#">
                <ec:InclusiveNamespaces PrefixList="SOAP-ENV cnt soap-sec soapenv sp cns wsdl wsp wsse wsu xs xsi" 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>...</ds:DigestValue>
          </ds:Reference>
          <ds:Reference URI="#id-3">
            <ds:Transforms>
              <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#">
                <ec:InclusiveNamespaces PrefixList="SOAP-ENV cnt soap-sec soapenv sp cns wsdl wsp wsse xs xsi" 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>...</ds:DigestValue>
          </ds:Reference>
          <ds:Reference URI="#id-4">
            <ds:Transforms>
              <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#">
                <ec:InclusiveNamespaces PrefixList="SOAP-ENV cnt soap-sec soapenv sp cns wsdl wsp wsu xs xsi" 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>...</ds:DigestValue>
          </ds:Reference>
          <ds:Reference URI="#id-5">
            <ds:Transforms>
              <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#">
                <ec:InclusiveNamespaces PrefixList="SOAP-ENV cnt soap-sec sp cns wsdl wsp wsse wsu xs xsi" 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>...</ds:DigestValue>
          </ds:Reference>
        </ds:SignedInfo>
        <ds:SignatureValue>
          ...
        </ds:SignatureValue>
        <ds:KeyInfo Id="KI-ABDCFEC7595B7819C213402151542862">
          <wsse:SecurityTokenReference wsu:Id="STR-ABDCFEC7595B7819C213402151542863">
            <wsse:Reference URI="#SecurityToken-5bf699c7-5336-4695-b395-88d2b984fe54" 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 wsu:Id="id-5">
    <ns5:bodyelement xmlns:ns4="http://namespace4.com/" xmlns:ns3="http://namespace3.com/" xmlns:ns2="http://bodynamespace2.com/" xmlns:ns5="http://namespace5.com/">
      ...
    </ns5:bodyelement>
  </soapenv:Body>
</soapenv:Envelope>

Here is some of the code that I have been trying (3 different methods for trying to get the uri fragment to work). I am only posting a portion of the code here, since it took over 200 lines of code to generate the appropriate XML, which I am now attempting to sign:

RSACryptoServiceProvider rsacsp = (RSACryptoServiceProvider)Key;
SignedXml xmlWSig = new SignedXml(myDoc);
xmlWSig.SigningKey = Key;
xmlWSig.SignedInfo.CanonicalizationMethod = SignedXml.XmlDsigExcC14NTransformUrl;
XmlDsigExcC14NTransform canMethod = (XmlDsigExcC14NTransform)xmlWSig.SignedInfo.CanonicalizationMethodObject;
canMethod.InclusiveNamespacesPrefixList = "SOAP-ENV cns soap-sec soapenv sp cnt wsdl wsp wsse wsu xs xsi";

Uri uri = new Uri("#id-1");
Reference ref1 = new Reference(uri.ToString());
XmlDsigExcC14NTransform transform1 = new XmlDsigExcC14NTransform("SOAP-ENV cns soap-sec soapenv sp cnt wsdl wsp wsse wsu xs xsi");
ref1.AddTransform(transform1);
ref1.DigestMethod = "http://www.w3.org/2001/04/xmlenc#sha256";
xmlWSig.AddReference(ref1);

Reference ref2 = new Reference("#id-2"); 
XmlDsigExcC14NTransform transform2 = new XmlDsigExcC14NTransform("SOAP-ENV cns soap-sec soapenv sp cnt wsdl wsp wsse wsu xs xsi");
ref2.AddTransform(transform2);
ref2.DigestMethod = "http://www.w3.org/2001/04/xmlenc#sha256";
xmlWSig.AddReference(ref2);

Reference ref3 = new Reference("");  
ref3.Uri = "#id-3";
XmlDsigExcC14NTransform transform3 = new XmlDsigExcC14NTransform("SOAP-ENV cns soap-sec soapenv sp cnt wsdl wsp wsse xs xsi");
ref3.AddTransform(transform3);
ref3.DigestMethod = "http://www.w3.org/2001/04/xmlenc#sha256";
xmlWSig.AddReference(ref3);

//repeat things for id-4, and id-5

KeyInfo myKeyInfo = new KeyInfo();
myKeyInfo.AddClause(new RSAKeyValue((RSA)Key));
xmlWSig.KeyInfo = myKeyInfo;

xmlWSig.ComputeSignature();
XmlElement signedXmlElement = xmlWSig.GetXml();

Key is the private key grabbed from the X509 Certificate (and is what should be used as the key to sign the document). myDoc is the System.Xml XmlDocument that I have generated that I need to insert the signature into.

Method #1 Gives me a System.UriFormatException: Invalid URI: The Format of the URI could not be determined.

Method #2 Gives me a System.Security.Cryptography.CryptograpicException: Malformed reference element (if I drop the # from the Uri, it gives me a System.UriFormatException: Invalid URI: The URI is empty).

Method #3 Gives me the same errors as Method #2.

From all of the documentation on using Uris for signatures, using just a Uri Fragment is allowed (assuming that the element being referenced is inside the same document), but the Uri class in C# doesn't seem to accept Fragments as an acceptable Uri.

The Reference class also seems to be requiring the full Uri, and not just a Fragment.

I'm open to any suggestions for how I can properly generate the signature in this XML using the specifications.

UPDATE: While the SignedXml + Reference + Transform seems like the best solution, I'm now beginning to believe that .NET has a major gap in these libraries, and dropping down to some lower-level libraries in order to generate the signature might be necessary.

Unfortunately, I'm still struggling with trying to tell what libraries would be needed and the algorithm for finding what needs to be signed. My understanding of the Exclusive Canonicalization was that you were only signing the elements specified by the prefixes listed in the InclusiveNamespaces PrefixList, but the URI in the Reference specifies the sub-document that the signature should be over, but the elements inside the specified elements don't use most of the included namespaces. Am I understanding the way that these References are supposed to work?

È stato utile?

Soluzione 2

Turns out that the reason .NET wasn't accepting it was because of the wsu namespace prefix. flipping the wsu:Id= to be just id= was able to generate the signature at least. Then, I came across this: 'Malformed Reference Element' when adding a reference based on an Id attribute with SignedXml class

This used drastically less code than my previous answer to develop, and is much easier to read/maintain.

Altri suggerimenti

Looks like I may have to duplicate the algorithm of doing the URI + the Inclusive Namespaces for myself (still need to figure out how that will work), and convert those elements into byte arrays. Then use lower-level libraries for signatures.

Something like this:

RSACryptoServiceProvider rsa = (RSACryptoServiceProvider)cert.PrivateKey;
signedMessage = rsa.SignData(originalMessage, CryptoConfig.MapNameToOID("SHA1"));

Then, signedMessage can be converted into a Base64 string, and inserted into each of the appropriate Digest Values.

A lot more work than I had hoped for, but if .NET doesn't support URI Fragments, I guess I have to do what I have to do.

Due to the complexity of this solution, I'm definitely open to alternatives if anybody has something smoother.

EDIT: After several hours of pouring over the canonicalization documentation, it looks like the exclusive canonicalization is more about what namespaces you insert into the XML when ripping it out to sign it. If the namespace isn't directly used, and isn't included in the inclusive namespace prefix list, you don't add that namespace to the elements that you are signing prior to signing them. This still seems odd to me, since you don't necessarily want that namespace in the element inside the fuller context of the XML, and signing it means that it can't change, but you're not including it.

UPDATE: after many hours of testing, I did finally get this to work. But it had lots, and lots of very hideous code. Essentially, I had to pull each element that we were signing out into a copy, and manually update the namespaces that were included in that element, and then generate the hash from that (including performing the more complete transform at this time). But it did work.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top