In this post, I showed how to verify XML digital signature or XmlDSig. In this post, I’m going to show how to sign an XML using a signing certificate.
For the XmlDSig to be valid, it requires a custom Filter transformation. This tells which node in the XML content to be signed.
public class XmlDsigXPathTransformWithFilter : XmlDsigXPathTransform
{
public const string FilterAlgorithm = "http://www.w3.org/2002/06/xmldsig-filter2";
private readonly string _filter;
public XmlDsigXPathTransformWithFilter(string filter)
{
_filter = filter;
Algorithm = FilterAlgorithm;
}
public static XmlDsigXPathTransformWithFilter Create(string xPathString)
{
var doc = new XmlDocument();
var xPathElem = doc.CreateElement("XPath", FilterAlgorithm);
xPathElem.InnerText = xPathString;
var xForm = new XmlDsigXPathTransformWithFilter("intersect");
xForm.LoadInnerXml(xPathElem.SelectNodes("."));
return xForm;
}
protected override XmlNodeList GetInnerXml()
{
var nodeList = base.GetInnerXml();
if (nodeList.Count != 1)
throw new InvalidOperationException("NodeList must contain one item");
if (!(nodeList[0] is XmlElement el))
throw new InvalidOperationException("NodeList item must be an XmlElement");
el.SetAttribute("xmlns", FilterAlgorithm);
el.SetAttribute("Filter", _filter);
return nodeList;
}
}
Then, we add a Signer class with SignXml method that uses the custom transform. The SignXml returns a Base64 string representation of the XmlDSig.
public class Signer
{
public static string SignXml(string xml, string filterValue, X509Certificate2 cert)
{
if (!filterValue.StartsWith("//"))
filterValue = "/" + filterValue;
if (!filterValue.StartsWith("//"))
filterValue = "/" + filterValue;
if (xml == null)
throw new ArgumentException(nameof(xml));
var xmlDoc = new XmlDocument
{
PreserveWhitespace = true
};
xmlDoc.LoadXml(xml);
var signedXml = new SignedXml(xmlDoc)
{
SigningKey = cert.PrivateKey,
KeyInfo = new KeyInfo()
};
signedXml.KeyInfo.AddClause(new KeyInfoX509Data(cert));
signedXml.KeyInfo.AddClause(new RSAKeyValue(cert.GetRSAPublicKey()));
var reference = new Reference
{
Uri = string.Empty
};
// Add a filter transformation
var filter = XmlDsigXPathTransformWithFilter.Create(filterValue);
reference.AddTransform(filter);
// Add an enveloped transformation to the reference.
var env = new XmlDsigEnvelopedSignatureTransform();
reference.AddTransform(env);
var can = new XmlDsigC14NWithCommentsTransform();
reference.AddTransform(can);
signedXml.AddReference(reference);
signedXml.ComputeSignature();
// Get the XML representation of the signature and save
// it to an XmlElement object.
var xmlDigitalSignature = signedXml.GetXml();
// Append the element to the XML document.
xmlDoc.DocumentElement.AppendChild(xmlDoc.ImportNode(xmlDigitalSignature, true));
var signed = xmlDoc.InnerXml;
var signed64 = Convert.ToBase64String(Encoding.UTF8.GetBytes(signed));
return signed64;
}
}
This is the result when we use this to sign an XML.

And the signature looks like this when decoded
