XmlIdSignedXml.cs
using System;
using System.Security.Cryptography.Xml;
using System.Xml;
/// <summary>
/// Provides xml:id support for XML digital signatures
/// </summary>
/// <remarks>
/// This class allows the .NET XML Digital Signature system
/// uniquely identify nodes based upon xml:id's. The xml:id
/// working draft can be found on the W3C's website:
/// https://www.w3.org/TR/2004/WD-xml-id-20040407/
/// </remarks>
public sealed class XmlIdSignedXml : SignedXml
{
/// <summary>
/// Namespace URI to map to the xml prefix
/// </summary>
public static readonly string XmlIdUrl = "https://www.w3.org/XML/1998/namespace";
private bool m_strict; // operate in strict mode?
/// <summary>
/// Create a signed XML class that can sign and verify signatures
/// using xml:id's
/// </summary>
/// <see cref='System.Security.Cryptography.Xml.SignedXml'/>
public XmlIdSignedXml() : base()
{
Strict = false;
return;
}
/// <summary>
/// Create a signed XML class that can sign and verify signatures
/// using xml:id's, using a specific document context
/// </summary>
/// <see cref='System.Security.Cryptography.Xml.SignedXml'/>
public XmlIdSignedXml(XmlDocument document) : base(document)
{
Strict = false;
return;
}
/// <summary>
/// Create a signed XML class that can sign and verify signatures
/// using xml:id's, initialized with an XML element
/// </summary>
/// <see cref='System.Security.Cryptography.Xml.SignedXml'/>
public XmlIdSignedXml(XmlElement element) : base(element)
{
Strict = false;
return;
}
/// <summary>
/// Flag to indicate if xml:id's should be matched exclusively
/// or if fallback on default behavior
/// </summary>
public bool Strict
{
get { return m_strict; }
set { m_strict = value; }
}
/// <summary>
/// Return the XmlElement with the given id from the given document
/// </summary>
/// <remarks>
/// First attempts to match to an xml:id, and if that fails will only
/// fall back on the default behavior if the Strict flag is unset.
/// </remarks>
/// <param name="document">document to search for matching nodes in</param>
/// <param name="idValue">id of the node to find</param>
/// <exception cref="System.ArgumentNullException"><paramref name="idValue"/> is null</exception>
/// <exception cref="System.ArgumentException"><paramref name="idValue"/> contains both single and double quotes</exception>
/// <exception cref="System.InvalidOperationException"><paramref name="idValue"/> matches multiple nodes</exception>
/// <returns>
/// null if no match is found
/// node with the given xml:id if one is found
/// node with the given id if no xml:id is found and Strict is false
/// </returns>
/// <see cref='Strict'/>
public override XmlElement GetIdElement(XmlDocument document, string idValue)
{
if(idValue == null)
throw new ArgumentNullException("idValue", "Need an ID value to search for");
if(document == null)
return null; // following the pattern defined in the default
// a null document provides no search results, but
// also does not throw an exception.
// setup the namespace mapping for the xml:id namespace
XmlNamespaceManager nsManager = new XmlNamespaceManager(document.NameTable);
nsManager.AddNamespace("xml", XmlIdUrl);
// quote the id to search for
string searchString = null;
if(idValue.IndexOf('\'') == -1)
searchString = "'" + idValue + "'";
else if(idValue.IndexOf('\"') == -1)
searchString = "\"" + idValue + "\"";
else
throw new ArgumentException("idValue", "Cannot search for an xml:id containing both single and double quotes.");
// get the nodes that have xml:ids which mach the given id
XmlNodeList xmlIdNodes = document.SelectNodes("//*[@xml:id=" + searchString + "]", nsManager);
// xml:id's must be unique in the document
if(xmlIdNodes.Count > 1)
throw new InvalidOperationException("Search for a non-unique xml:id");
// we found an xml:id that matches, so return that one
else if(xmlIdNodes.Count == 1)
return xmlIdNodes[0] as XmlElement;
// there are no matching xml:id's, if strict matching was requested fail, otherwise
// default to the SignedXml search
if(Strict)
return null;
else
return base.GetIdElement(document, idValue);
}
}