XMLSigner No longer works in 4.6.2 - Malformed reference element
The relevant piece of code - CalculateHashValue
of System.Security.Cryptography.Xml.Reference
:
// for "Head01" this is "#Head01"if (this.m_uri[0] == '#'){ // idFromLocalUri is set to "Head01" string idFromLocalUri = Utils.GetIdFromLocalUri(this.m_uri, out flag); ... // there is no element with Id="Head01" - so xmlElement is null var xmlElement = this.SignedXml.GetIdElement(document, idFromLocalUri); ... if (xmlElement == null) { // this is the error you're getting throw new CryptographicException(SecurityResources.GetResourceString("Cryptography_Xml_InvalidReference")); }}
So you fail on reference validation - there is no element with this id in document - btw. experimentally changing "_ID" to "Id" in your xml fixed the issue.
Good news is the SignedXml
class is extensible and you can overload the XmlElement GetIdElement(XmlDocument document, string idValue)
method to take "_ID" into an account.
// just a sampleclass MyCustomSignedXml : SignedXml { ... override XmlElement GetIdElement(XmlDocument document, string idValue) { var element = document.SelectSingleNode($"//*[@_ID='{idValue}']") as XmlElement; if (element != null) { return element; } return base.GetIdElement(document, idValue); }}
With Ondrej Svejdar's hints I was able to get this working. It turns out I needed two classes for this to work. I haven't been able to test in UAT, but so far I needed two classes, and a registry edit. A Custom XmlUrlResolver to allow the DTDs to be in a separate location and point to the same folder as the XML for the external references, and a modified SignedXml class to handle IDs.
Registry edit: https://support.microsoft.com/en-us/help/3148821/after-you-apply-security-update-3141780-net-framework-applications-enc
Modified SignedXml class:
public class CustomIdSignedXml : SignedXml{ private static readonly string[] idAttrs = new string[] { "_id", "_Id", "_ID" }; public CustomIdSignedXml(XmlDocument doc) : base(doc) { return; } public override XmlElement GetIdElement(XmlDocument doc, string id) { XmlElement idElem = null; // check to see if it's a standard ID reference //XmlElement idElem = base.GetIdElement(doc, id); //if (idElem != null) // return idElem; //I get the feeling this is horridly insecure XmlElement elementById1 = doc.GetElementById(id); if (elementById1 != null) return elementById1; // if not, search for custom ids foreach (string idAttr in idAttrs) { idElem = doc.SelectSingleNode("//*[@" + idAttr + "=\"" + id + "\"]") as XmlElement; if (idElem != null) break; } return idElem; }}
Modified XmlResolver:
public class DTDAndSignatureResolver : XmlUrlResolver{ private readonly Uri DTDUri; private readonly List<string> XmlExtensions = new List<string>() { ".xml" }; private readonly List<string> DTDExtensions = new List<string>() { ".dtd", ".ent" }; private ICredentials credentials; public DTDAndSignatureResolver(Uri DTDUri) { this.DTDUri = DTDUri; } public override ICredentials Credentials { set { credentials = value; } } public override object GetEntity(Uri absoluteUri, string role, Type ofObjectToReturn) { if (DTDExtensions.Any(e => absoluteUri.ToString().ToLower().EndsWith(e)) || XmlExtensions.Any(e => absoluteUri.ToString().ToLower().EndsWith(e))) { return base.GetEntity(absoluteUri, role, ofObjectToReturn); //For DTD/ENT/XML lookup } else { return base.GetEntity(DTDUri, null, ofObjectToReturn); //For signature image lookup } } public override Uri ResolveUri(Uri uri, string relativeUri) { return base.ResolveUri(DTDUri, relativeUri); }}
With both of these modifications my .Net 4.6.2 code was able to verify the Signed XML documents from .Net 3.5, and vice versa.