Skip to content

Instantly share code, notes, and snippets.

@dschulz
Created December 30, 2023 21:38
Show Gist options
  • Save dschulz/f4cec4255610129a99a75c390fd96c9c to your computer and use it in GitHub Desktop.
Save dschulz/f4cec4255610129a99a75c390fd96c9c to your computer and use it in GitHub Desktop.
Ejemplo básico C# XML-Dsig Ekuatia
using System.Text.RegularExpressions;
using System.Xml;
namespace ConsoleApp1;
using System.Security.Cryptography.Xml;
public partial class EkuatiaSignedXml : SignedXml
{
public EkuatiaSignedXml(XmlDocument document) : base(document)
{
if ( SignedInfo is not null ){
SignedInfo.CanonicalizationMethod = SignedXml.XmlDsigExcC14NTransformUrl;
}
}
public EkuatiaSignedXml(XmlElement elem) : base(elem)
{
if ( SignedInfo is not null ){
SignedInfo.CanonicalizationMethod = SignedXml.XmlDsigExcC14NTransformUrl;
}
}
public override XmlElement? GetIdElement(XmlDocument? document, string idValue)
{
if (document is null)
return null;
if (string.IsNullOrEmpty(idValue))
return null;
var match = CuarentaYCuatroDigitos().Match(idValue.Trim());
if(!match.Success)
{
return base.GetIdElement(document, idValue);
}
var elementName = "DE";
var xPath = $"//*[local-name()=\"{elementName}\" and (@Id=\"{idValue}\" or @ID=\"{idValue}\" or @id=\"{idValue}\" or @iD=\"{idValue}\")]";
var nodeList = document.SelectNodes(xPath);
if (nodeList is null || nodeList.Count==0)
return null;
return nodeList.Item(0) as XmlElement;
}
[GeneratedRegex("\\d{44}")]
private static partial Regex CuarentaYCuatroDigitos();
}

using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Security.Cryptography.Xml;
using System.Text;
using System.Xml;
namespace ConsoleApp1;
public static class Prueba
{
public static void Main(string[] args)
{
try
{
// Generar una clave descartable
var key = RSA.Create();
// Crear un XML para firmar
CrearXmlPrueba("prueba.xml");
Console.WriteLine("XML de prueba creado.");
FirmarArchivoXml("prueba.xml", "prueba-firmado.xml", key);
Console.WriteLine("XML firmado.");
Console.WriteLine("Vericando firma con la misma clave con la que fue firmado el XML...");
var result = VerificarFirmaEnArchivoXml("prueba-firmado.xml");
Console.WriteLine(result ? "La firma es válida." : "La firma es inválida.");
}
catch (CryptographicException e)
{
Console.WriteLine(e.Message);
}
}
// Aplicar firma y guardar en un archivo nuevo
private static void FirmarArchivoXml(string archivoXml, string salidaXmlFirmado, RSA clave)
{
var doc = new XmlDocument
{
PreserveWhitespace = false
};
// Cargar el archivo
doc.Load(new XmlTextReader(archivoXml));
// Crear un objeto de la subclase de SignedXml
var xmlFirmado = new EkuatiaSignedXml(doc)
{
SigningKey = clave,
};
// Fijar el CanonicalizationMethod
if (xmlFirmado.SignedInfo != null)
xmlFirmado.SignedInfo.CanonicalizationMethod = SignedXml.XmlDsigExcC14NTransformUrl;
var cdc = ExtraerCdc(doc);
var reference = new Reference
{
Uri = "#" + cdc
};
reference.AddTransform(new XmlDsigEnvelopedSignatureTransform());
reference.AddTransform(new XmlDsigExcC14NTransform());
xmlFirmado.AddReference(reference);
var keyInfo = new KeyInfo();
var x509Data = new KeyInfoX509Data();
// Un objeto X509Certificate apropiado debe proveerse
// acá generamos un certificado al vuelo
var certificado = CrearCertificadoAutofirmado(clave);
x509Data.AddCertificate(certificado);
//
// El nodo <KeyInfo> debe contener solamente
// <X509Data>
// <X509Certificate>
// [contenido base64 del certificado]
// </X509Certificate>
// </X509Data>
//
keyInfo.AddClause(x509Data);
Console.WriteLine(keyInfo.GetXml().OuterXml);
xmlFirmado.KeyInfo = keyInfo;
// Firmar
xmlFirmado.ComputeSignature();
// Obtener la representación XML de la firma en un objeto XmlElement
var xmlDigitalSignature = xmlFirmado.GetXml();
// Agregar el elemento al documento
doc.DocumentElement?.AppendChild(doc.ImportNode(xmlDigitalSignature, true));
if (doc.FirstChild is XmlDeclaration) doc.RemoveChild(doc.FirstChild);
// Guardar el documento en el archivo especificado
var xmlTextWriter = new XmlTextWriter(salidaXmlFirmado, new UTF8Encoding(false));
doc.WriteTo(xmlTextWriter);
xmlTextWriter.Close();
}
private static string? ExtraerCdc(XmlDocument xmlDocument)
{
// Crear un namespace manager y agregar el namespace por defecto
var namespaceManager = new XmlNamespaceManager(xmlDocument.NameTable);
namespaceManager.AddNamespace("ns", "http://ekuatia.set.gov.py/sifen/xsd");
// Seleccionar el nodo <DE> usando una expresión XPath con el namespace
var deNode = xmlDocument.SelectSingleNode("//ns:DE", namespaceManager);
var idAttribute = deNode?.Attributes?["Id"] ;
return idAttribute?.Value;
}
/*
* Esto es solo para pruebas
*/
private static X509Certificate CrearCertificadoAutofirmado(RSA key)
{
var certificateRequest = new CertificateRequest(
new X500DistinguishedName("CN=PruebaPruebaPrueba"),
key,
HashAlgorithmName.SHA256,
RSASignaturePadding.Pkcs1);
// Fijar los usos del certificado
certificateRequest.CertificateExtensions.Add(
new X509KeyUsageExtension(X509KeyUsageFlags.KeyEncipherment | X509KeyUsageFlags.DigitalSignature, false));
// Periodo de validez
var notBefore = DateTimeOffset.UtcNow;
var notAfter = notBefore.AddYears(10);
// Crear y retornar certificado X.509
return certificateRequest.CreateSelfSigned(notBefore, notAfter);
}
// Verificar la firma en un archivo XML y retornar el resultado
private static bool VerificarFirmaEnArchivoXml(string name)
{
var xmlDocument = new XmlDocument
{
PreserveWhitespace = true
};
xmlDocument.Load(name);
var signedXml = new SignedXml(xmlDocument);
var nodeList = xmlDocument.GetElementsByTagName("Signature");
if (nodeList.Count < 1)
{
return false;
}
var signNode = nodeList.Item(0);
if (signNode == null)
{
return false;
}
Console.Error.WriteLine("Verificando ...\n---\n" + signNode.OuterXml + "\n---\n");
signedXml.LoadXml((XmlElement) signNode);
return signedXml.CheckSignature();
}
private const string NamespaceEkuatia = "http://ekuatia.set.gov.py/sifen/xsd";
private const string NamespaceXsi = "http://www.w3.org/2001/XMLSchema-instance";
private const string XsdUriSiRecepDe = "https://ekuatia.set.gov.py/sifen/xsd siRecepDE_v150.xsd";
// Crear un XML de prueba para firmar
private static void CrearXmlPrueba(string fileName)
{
var document = new XmlDocument
{
PreserveWhitespace = false
};
var node = document.CreateElement( "rDE", NamespaceEkuatia);
node.SetAttribute("xmlns:xsi", NamespaceXsi);
node.SetAttribute("xmlns", NamespaceEkuatia);
node.SetAttribute("schemaLocation", NamespaceXsi, XsdUriSiRecepDe);
// Agregar un poco de basura al nodo
var de = document.CreateElement("DE", NamespaceEkuatia);
de.SetAttribute("Id", "01800022017001001609722522023103114566901544");
node.AppendChild(de);
// Agregar el nodo al documento
document.AppendChild(node);
var xmlWriterSettings = new XmlWriterSettings
{
Indent = true,
IndentChars = " ", // indentar con 4 espacios
NewLineChars = Environment.NewLine,
NewLineHandling = NewLineHandling.Replace,
Encoding = new UTF8Encoding(true),
OmitXmlDeclaration = true
};
using var xmlToWrite = XmlWriter.Create(fileName, xmlWriterSettings);
document.WriteTo(xmlToWrite);
xmlToWrite.Close();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment