Created
December 30, 2023 21:38
-
-
Save dschulz/f4cec4255610129a99a75c390fd96c9c to your computer and use it in GitHub Desktop.
Ejemplo básico C# XML-Dsig Ekuatia
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| |
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