26

Unable to sign element by Id attribute when there's a namespace prefix:

void Main()
{
    var doc = new XmlDocument();
    doc.LoadXml("<root xmlns:u=\"myuri\"><test u:Id=\"_0\">Zebra</test></root>");

    SignedXml signedXml = new SignedXml(doc);
    signedXml.SigningKey = new RSACryptoServiceProvider();

    Reference reference = new Reference("#_0");
    signedXml.AddReference(reference);

    signedXml.ComputeSignature();
}

ComputeSignature() will fail here with 'Malformed Reference Element' how should this be done?

Perception
  • 79,279
  • 19
  • 185
  • 195
Dog Ears
  • 9,637
  • 5
  • 37
  • 54

4 Answers4

58

The approach we used was to subclass System.Security.Cryptography.Xml.SignedXml class...

public class SignedXmlWithId : SignedXml
{
    public SignedXmlWithId(XmlDocument xml) : base(xml)
    {
    }

    public SignedXmlWithId(XmlElement xmlElement) 
        : base(xmlElement)
    {       
    }

    public override XmlElement GetIdElement(XmlDocument doc, string id)
    {
        // check to see if it's a standard ID reference
        XmlElement idElem = base.GetIdElement(doc, id);

        if (idElem == null)
        {
            XmlNamespaceManager nsManager = new XmlNamespaceManager(doc.NameTable);
            nsManager.AddNamespace("wsu", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd");

            idElem = doc.SelectSingleNode("//*[@wsu:Id=\"" + id + "\"]", nsManager) as XmlElement;
        }

        return idElem;
    }
}
Dog Ears
  • 9,637
  • 5
  • 37
  • 54
  • 3
    You are my hero for today. Thank you. – Bon Feb 17 '16 at 16:24
  • 4
    I had the same error, but the problem was that the value for SecurePart\Id started with number (only characters are allowed). – Jan Friedrich Mar 11 '16 at 09:39
  • 2
    @JanFriedrich you should post a working answer, I'll willing accept that if correct. – Dog Ears Mar 11 '16 at 09:53
  • 2
    @JanFriedrich, that was my issue as well. I was working with an ID for a SAML request. Had to conform to the XML ID convention. See https://www.w3.org/TR/xml-id/ which is an NCNAME https://www.w3.org/TR/xml-names11/#NT-NCName, which the starting character is restricted. I used a GUID with no braces, just prepended it with an _ for the ID. – Michael Ritchson Dec 09 '19 at 18:01
  • I had the same issue but no control over the ID since that was generated by another system. As a fix I just overrode GetIdElement to run the same code as the base implementation (copied from source.dot.net), but with the NCName validation snippet removed. – ChaseMedallion Oct 31 '22 at 21:33
  • This was the insight I needed to finally create the signature for our payload! Thank you thank you thank you. – Tyler StandishMan Aug 23 '23 at 15:21
3

var reference = new Reference(""); // This will sign the entire document

Richard Schneider
  • 34,944
  • 9
  • 57
  • 73
  • 1
    But I need to supply the id of the element to sign..the actual xml is a soap envelope and using Ids seems to be the way that it's done. – Dog Ears Feb 24 '11 at 07:09
  • ok, the xml has atttibute "u:Id", if you change it to "...", then ComputeSignature should work. If you can not change the input then you will have use a "#xpointer" reference. – Richard Schneider Feb 24 '11 at 20:10
2

It should be noted that you will need to use SignedXmlWithId object instead of SignedXml object in order to be able to use the overridden GetIdElement() method. Once I did that, I was able to sign an XmlElement and get around the Malformed Reference Element error.

See my post about this topic here.

Community
  • 1
  • 1
Russ
  • 678
  • 8
  • 26
0

SignedXml does not recognize u:Id as a valid XML ID, and the XML Signature does require it to be an XML ID.

You can either use the Schema (http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd if your trying to use a WS-Security Id) or add an DTD to the XML fragment. ( ]> for an XML fragment). Adding a DTD to just your LoadXml will make SignedXml recognize the Id, but since SOAP does not allow DTD's, don't include the DTD in your on-the-wire SOAP.