I'm using C# with .NET 9, and I need to digitally sign a XML document using a X.509 certificate. The signing process itself works perfectly. However, I've been given a very specific requirement: the <Signature>
tag, which is automatically generated by .NET when computing the signature, needs to have a sch:
prefix for some reason, and also the namespace needs to be different.
After a lot of trial and error I've managed to make all of that happen. However, a single line of code invalidates the signature, which is:
signedXmlWithSchPrefix.SetAttribute("xmlns:sch", customNamespace);
I need to do this in order to change the namespace to the custom one.
My question: is there any way to "tell" .NET to generate the <Signature>
tag with a specific prefix and specific namespace, rather than following the standard? Because I can't change the tag after it's generated, the only way I can imagine this would work is if .NET generates the tag following my requirements.
Here is my current implementation:
public static string SignWithCertificate(string xmlFilePath, string certThumbprint)
{
// Busca o certificado
X509Certificate2 cert = GetCertificateFromLocalMachine(certThumbprint);
if (cert == null || !cert.HasPrivateKey)
throw new Exception("Certificado não encontrado.");
// Faz o parse do XML
XmlDocument xmlDoc = new() { PreserveWhitespace = true };
xmlDoc.LoadXml(File.ReadAllText(xmlFilePath));
if (xmlDoc.DocumentElement == null)
throw new ArgumentException("Falta o elemento raíz do documento XML.");
// Define o prefixo e namespace no documento ANTES de assinar
string customPrefix = "sch";
string customNamespace = ";;
XmlNamespaceManager nsManager = new(xmlDoc.NameTable);
nsManager.AddNamespace(customPrefix, customNamespace);
// Adiciona as referências/transforms
Reference reference = new() { Uri = "" };
reference.AddTransform(new XmlDsigEnvelopedSignatureTransform());
// Prepara a assinatura
SignedXml signedXml = new(xmlDoc) { SigningKey = cert.GetRSAPrivateKey() };
Signature xmlSignature = signedXml.Signature;
xmlSignature.SignedInfo.AddReference(reference);
KeyInfo keyInfo = new();
keyInfo.AddClause(new KeyInfoX509Data(cert));
xmlSignature.KeyInfo = keyInfo;
// Gera a assinatura
signedXml.ComputeSignature();
XmlElement signedXmlWithSchPrefix = signedXml.GetXml();
signedXmlWithSchPrefix.SetAttribute("xmlns:sch", customNamespace); // <--- THIS SINGLE LINE INVALIDATES THE SIGNATURE
signedXmlWithSchPrefix.Prefix = customPrefix;
// Prepara o elemento XML que irá conter os dados da assinatura
string targetNodeName = "sch:envioDocumentoWS";
XmlNode targetNode = xmlDoc.GetElementsByTagName(targetNodeName).Item(0) ??
throw new Exception($"Tag <{targetNodeName}> não encontrada no XML.");
// Adiciona a assinatura no documento
XmlNode signatureNode = xmlDoc.ImportNode(signedXmlWithSchPrefix, true);
targetNode.AppendChild(signatureNode);
// Salva o XML assinado (mesmo nome do arquivo original, seguido de .signed.xml)
return SaveSignedXml(xmlDoc, xmlFilePath);
}