Search code examples
c#xmlsignaturexml-signatureebics

Incorrect reference element signature XML C#


I need to implement the EBICS Protocol, particulary the HPB request and i need to sign my XML File :

    <?xml version="1.0" encoding="UTF-8"?>
<ebicsNoPubKeyDigestsRequest xmlns="http://www.ebics.org/H003" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.ebics.org/H003 http://www.ebics.org/H003/ebics_keymgmt_request.xsd" Version="H003" Revision="1">
  <header authenticate="true">
    <static>
      <HostID>EBIXQUAL</HostID>
      <Nonce>234AB2340FD2C23035764578FF3091C1</Nonce>
      <Timestamp>2015-11-13T10:32:30.123Z</Timestamp>
      <PartnerID>AD598</PartnerID>
      <UserID>EF056</UserID>
      <OrderDetails>
        <OrderType>HPB</OrderType>
        <OrderAttribute>DZHNN</OrderAttribute>
      </OrderDetails>
      <SecurityMedium>0000</SecurityMedium>
    </static>
    <mutable />
  </header>
</ebicsNoPubKeyDigestsRequest>

So i need to sign the elements where

authenticate="true"

To sign my document in C# i wrote this code :

XmlDocument xmlDoc = new XmlDocument();
        xmlDoc.PreserveWhitespace = false;
        xmlDoc.Load("hpbtest.xml");
        RSA Key = new GestionCertificat("CN=XML_ENC_TEST_CERT4").getClePrivee();
        // Create a SignedXml object.
        SignedXml signedXml = new SignedXml(xmlDoc);
        // Add the key to the SignedXml document.
        signedXml.SigningKey = Key;
        // Create a reference to be signed.
        Reference reference = new Reference();
        reference.Uri = "#xpointer(//*[@authenticate='true'])";
        // Add an enveloped transformation to the reference.
        XmlDsigExcC14NTransform env = new XmlDsigExcC14NTransform();
        reference.AddTransform(env);
        // Add the reference to the SignedXml object.
        signedXml.AddReference(reference);
        // Compute the signature.
        signedXml.ComputeSignature();
        // Get the XML representation of the signature and save
        // it to an XmlElement object.
        XmlElement xmlDigitalSignature = signedXml.GetXml();
        // Append the element to the XML document.
        xmlDoc.DocumentElement.AppendChild(xmlDoc.ImportNode(xmlDigitalSignature, true));
        xmlDoc.Save("hpbtest.xml");

But when i try to sign it, i get this error on line

signedXml.ComputeSignature()

:

Incorrect reference element

Could you help me to solve my problem please ?

Thank you in advance !

Thomas !


Solution

  • I reverse engineered the XPointer operations by SignedXml and Reference classes and.. I can give you all the details in a seperate answer, but what I conclude for now is, you can only have two types of queries:

    #xpointer(/)
    

    This works because it is explicitly checked for, and

    #xpointer(id(
    

    This is again explicitly (with string.StartsWith) checked.

    Therefore, the only way to achieve this, as you pointed out in your comment, seems like to extend the SignedXml class and to override the GetIdElement method as follows:

    public class CustomSignedXml : SignedXml
    {
        XmlDocument xmlDocumentToSign;
    
        public CustomSignedXml(XmlDocument xmlDocument) : base(xmlDocument)
        {
            xmlDocumentToSign = xmlDocument;
        }
    
        public override XmlElement GetIdElement(XmlDocument document, string idValue)
        {
            XmlElement matchingElement = null;
            try
            {
                matchingElement = base.GetIdElement(document, idValue);
            }
            catch (Exception idElementException)
            {
                Trace.TraceError(idElementException.ToString());
            }
    
            if (matchingElement == null)
            {
                // at this point, idValue = xpointer(//*[@authenticate='true'])
                string customXPath = idValue.TrimEnd(')');
                customXPath = customXPath.Substring(customXPath.IndexOf('(') + 1);
                matchingElement = xmlDocumentToSign.SelectSingleNode(customXPath) as XmlElement;
            }
    
            return matchingElement;
        }
    }
    

    Then in the consumer code, just change the SignedXml to CustomSignedXml:

    CustomSignedXml signedXml = new CustomSignedXml(xmlDoc);