
31/03/2011 11:56 por
zorry
En los anteriores artículos de esta serie pudimos ver la estructura de un XML fimado mediante XAdES, y el modo de validar una firma y generar un certificado de pruebas. Por fin, en este tercer artículo de la serie, vamos a firmar documentos. Primero realizaremos una firma XmlDSig y posteriormente ampliaremos esta firma para obtener una firma XAdES correcta.
Para realizar una firma compatible con XmlDSig no tenemos más que emplear la clase SignedXml de .Net, y proporcionarle el Xml que queremos firmar, la clave privada con la que firmaremos y una referencia al nodo que queremos firmar. Haremos una prueba unitaria que nos valide esta operativa:
[TestMethod]
public void TestXmlDSigSignature()
{
Signature sig = new Signature();
X509Certificate2 cert = sig.GetCertificateFromStore(StoreLocation.CurrentUser, StoreName.My,
"91 15 78 ac 78 8a 20 4d e2 33 96 4a c7 90 c1 36 c0 db ad df");
XmlDocument doc = new XmlDocument();
doc.LoadXml("<?xml version=\"1.0\" encoding=\"UTF-8\"?><documento id=\"documento\"><titulo id=\"titulo\">Documento de pruebas</titulo><descripcion id=\"descripcion\">Documento destinado a realizar pruebas de firma</descripcion></documento>");
XmlDocument signed = sig.SignDocument(cert, doc);
sig.Validate(signed);
}
Vamos a realizar una firma Enveloped. Para ello implementamos el siguiente método:
public XmlDocument SignDocument(X509Certificate2 cert, XmlDocument doc)
{
SignedXml signer = new SignedXml(doc);
//Set the key to sign
signer.SigningKey = cert.PrivateKey;
signer.KeyInfo = getKeyInfo(signer, cert);
//Set nodes idetifiers
signer.Signature.Id = "SignatureUsuario";
signer.KeyInfo.Id = "KeyInfo";
//Set canonicalization method for signature
signer.SignedInfo.CanonicalizationMethod = SignedXml.XmlDsigExcC14NTransformUrl;
//Set the reference to sign
signer.AddReference(setCertificationReference(signer));
//Compute signature
signer.ComputeSignature();
//Construct the Signed Xml and return it
doc.DocumentElement.AppendChild(doc.ImportNode(signer.GetXml(), true));
return doc;
}
El método es bastante sencillo de seguir con los comentarios, con lo que no me extenderé en describir cada paso realizado. Tenemos que tener en cuenta que queremos incluir en la firma todo el XML excluyendo el nodo Signature de la firma, de modo que creamos una referencia al contenido del XML mediante una sentencia Xpath. Para ello, vamos a crear el método que nos realizará esta referencia:
private Reference setCertificationReference(SignedXml signer)
{
Reference reference = new Reference(String.Empty);
// create the XML that represents the transform
XmlDocument doc = new XmlDocument();
doc.LoadXml("<XPath xmlns:ds=\"http://www.w3.org/2000/09/xmldsig#\">not(ancestor-or-self::ds:Signature)</XPath>");
XmlDsigXPathTransform xform = new XmlDsigXPathTransform();
xform.LoadInnerXml(doc.DocumentElement.SelectNodes("."));
reference.TransformChain = new TransformChain();
reference.TransformChain.Add(xform);
reference.TransformChain.Add(new XmlDsigExcC14NTransform());
return reference;
}
private KeyInfo getKeyInfo(SignedXml signer, X509Certificate2 cert)
{
KeyInfo keyInfo = new KeyInfo();
KeyInfoX509Data keyInfoClause = new KeyInfoX509Data(cert);
keyInfoClause.AddSubjectName(cert.SubjectName.Name);
keyInfo.AddClause(keyInfoClause);
return keyInfo;
}
Ejecutamos la prueba unitaria y comprobaremos que la firma ser ealiza y se valida correctamente. Este es el XML resultante (tras formatearlo con un editor de texto):
<?xml version="1.0" encoding="UTF-8"?>
<documento id="documento">
<titulo id="titulo">Documento de pruebas</titulo>
<descripcion id="descripcion">Documento destinado a realizar pruebas de firma</descripcion>
<Signature Id="SignatureUsuario" xmlns="http://www.w3.org/2000/09/xmldsig#">
<SignedInfo>
<CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
<SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1" />
<Reference URI="">
<Transforms>
<Transform Algorithm="http://www.w3.org/TR/1999/REC-xpath-19991116">
<XPath xmlns:ds="http://www.w3.org/2000/09/xmldsig#">not(ancestor-or-self::ds:Signature)</XPath>
</Transform>
<Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
</Transforms>
<DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />
<DigestValue>EhaeWtAs6PEFAMHhHlJDT509kNE=</DigestValue>
</Reference>
</SignedInfo>
<SignatureValue>vN+qTDrnITHaRy94qCJhur1/fZHNJPXh5eT6bkEFh6EQRigI3ZFywO0fllnegX3CBoRhLFiaGbwB2upbpRLMEovzKTjZ4xB/DXmym5KZSPcyO7Fh25iq4L3VIETkGf4zV4cef7ZtDTN2lvKdoKnPkNFNQXXYcxS7WxIIvLKSg2c=</SignatureValue>
<KeyInfo Id="KeyInfo">
<X509Data>
<X509SubjectName>CN=TestCertificate, OU=Testing, OU=local</X509SubjectName>
<X509Certificate>MIIB2TCCAYegAwIBAgIQZfvBf6QChY9MxURe7gvy3jAJBgUrDgMCHQUAMBYxFDASBgNVBAMTC1Jvb3QgQWdlbmN5MB4XDTExMDMyOTA5MTQyNFoXDTM5MTIzMTIzNTk1OVowPDEOMAwGA1UECxMFbG9jYWwxEDAOBgNVBAsTB1Rlc3RpbmcxGDAWBgNVBAMTD1Rlc3RDZXJ0aWZpY2F0ZTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAyCpLpQjTmY1rymBnrBMj48gVWwseQdqLj47baOX5/dSRNMi8DdTBWV9eHryi4l1ivoG65AD8GHWcMoqiEQ2ZyjhHD/yR2CX5sd6Dm26e+caZIZ7mqm+wD4brR6NpNcJtIPBR2HYiHLPctCHffFmr16hOI6DlYJ5UoBm5TElw7HkCAwEAAaNLMEkwRwYDVR0BBEAwPoAQEuQJLQYdHU8AjWEh3BZkY6EYMBYxFDASBgNVBAMTC1Jvb3QgQWdlbmN5ghAGN2wAqgBkihHPuNSqXDX0MAkGBSsOAwIdBQADQQBTpAn4LfxKfCosDnDQDu6A4GhG8QWKjo9faHkS2xqajiYAiWf76oCIqj1hudcS94xfCp3gaRtajaUaB/ceVa5s</X509Certificate>
</X509Data>
</KeyInfo>
</Signature>
</documento>
Podemos comprobar entonces la sencillez de .Net para realizar una firma en .Net, ya que el objeto SignedXml nos construye para nosotros todo el XML de firma, teniendo solamente que incrustar manualmente el nodo Signature dentro del Xml.
Como vimos en el primer artículo de la serie, XAdES es un protocolo que se basa en XmlDsig, por lo que simplemente tendremos que añadir las partes que necesitamos para cumplimentar el estándar. En concreto, necesitamos:
- Añadir un objeto en el cual se describa el certificado y su emisor.
- Añadir referencias a este objeto recién creado, así como al nodo KeyInfo.
Empezamos como antes, realizando una prueba unitaria que nos permita validar nuestro desarrollo:
[TestMethod]
public void TestXAdESSignature()
{
Signature sig = new Signature();
X509Certificate2 cert = sig.GetCertificateFromStore(StoreLocation.CurrentUser, StoreName.My,
"91 15 78 ac 78 8a 20 4d e2 33 96 4a c7 90 c1 36 c0 db ad df");
XmlDocument doc = new XmlDocument();
doc.LoadXml("<?xml version=\"1.0\" encoding=\"UTF-8\"?><documento id=\"documento\"><titulo id=\"titulo\">Documento de pruebas</titulo><descripcion id=\"descripcion\">Documento destinado a realizar pruebas de firma</descripcion></documento>");
XmlDocument signed = sig.SignDocumentXAdES(cert, doc);
sig.Validate(signed);
}
Y desarrollamos el método de firma XAdES, que como se puede observar, es muy similar al que ya desarrollamos antes:
private static XmlDocument SignDocument(X509Certificate2 cert, XmlDocument toSign)
{
SignedXml signer = new SignedXml(toSign);
//Set the key to sign
signer.SigningKey = cert.PrivateKey;
signer.KeyInfo = getKeyInfo(signer, cert);
//Set nodes idetifiers
signer.Signature.Id = "SignatureUsuario";
signer.KeyInfo.Id = "KeyInfo";
//Set canonicalization method for signature
signer.SignedInfo.CanonicalizationMethod = SignedXml.XmlDsigExcC14NTransformUrl;
//Add XAdES node
addXAdESNodes(signer, toSign, cert);
//Set the references to sign
signer.AddReference(setCertificationReference(signer));
signer.AddReference(setXAdESReference(signer));
signer.AddReference(setKeyInfoReference(signer));
//Compute signature
signer.ComputeSignature();
//Construct the Signed Xml and return it
toSign.DocumentElement.AppendChild(toSign.ImportNode(signer.GetXml(), true));
return toSign;
}
La principal peculiaridad es que hay que construir el objeto XAdES a mano, esto es, construir el nodo XML mediante DOM. De esto se encarga el método addXAdESNode (cuyas “tripas”, por su extensión, no reflejaré aquí por brevedad, sólo la parte importante del mismo):
DataObject dataObject = new DataObject();
XmlElement result = document.CreateElement("etsi", "QualifyingProperties", "http://uri.etsi.org/01903/v1.3.2#");
//You must add all the nodes into result XmlElement with DOM
//Set the attributes and add to SignedXml class
result.SetAttribute("Target", signedXml.Signature.Id);
dataObject.Data = result.SelectNodes(".");
dataObject.Id = "XADES"; signedXml.AddObject(dataObject);
En este trozo de código estamos creando un objeto dentro del cual introduciremos el nodo XAdES construido a mano. El XML resultante (generado con los métodos auxiliares omitidos) queda como sigue:
<Object Id="XADES">
<etsi:QualifyingProperties Target="SignatureUsuario" xmlns:etsi="http://uri.etsi.org/01903/v1.3.2#">
<etsi:SignedProperties Id="XADES-Properties">
<etsi:SignedSignatureProperties>
<etsi:SigningTime>2011-03-31T10:56:13Z</etsi:SigningTime>
<etsi:SigningCertificate>
<etsi:Cert>
<etsi:CertDigest>
<ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" />
<ds:DigestValue xmlns:ds="http://www.w3.org/2000/09/xmldsig#">kRV4rHiKIE3iM5ZKx5DBNsDbrd8=</ds:DigestValue>
</etsi:CertDigest>
<etsi:IssuerSerial>
<ds:X509IssuerName xmlns:ds="http://www.w3.org/2000/09/xmldsig#">CN=Root Agency</ds:X509IssuerName>
<ds:X509SerialNumber xmlns:ds="http://www.w3.org/2000/09/xmldsig#">65FBC17FA402858F4CC5445EEE0BF2DE</ds:X509SerialNumber>
</etsi:IssuerSerial>
</etsi:Cert>
</etsi:SigningCertificate>
</etsi:SignedSignatureProperties>
</etsi:SignedProperties>
</etsi:QualifyingProperties>
</Object>
No te preocupes, los métodos auxiliares podrás verlos en el adjunto del último artículo de la serie. Simplemente están omitidos por mejorar la legibilidad del mismo. Lo importante es entender que este XML hay que montarlo manualmente.
Posteriormente, necesitaremos implementar los dos métodos que crean las referencias a la clave de firma y al objeto XADES:
private static Reference setXAdESReference(SignedXml signer)
{
Reference reference = new Reference("#XADES-Properties");
reference.Id = "SignatureUsuario-XADES-Properties-Ref";
reference.Type = "http://uri.etsi.org/01903/v1.2.2#SignedProperties";
reference.AddTransform(new XmlDsigExcC14NTransform());
return reference;
}
private static Reference setKeyInfoReference(SignedXml signedXml)
{
Reference reference = new Reference("#" + signedXml.KeyInfo.Id);
reference.AddTransform(new XmlDsigExcC14NTransform());
reference.Id = "SignatureUsuario-KeyInfo-Ref";
return reference;
}
Una vez que terminamos de implementar estos dos métodos, podremos ejecutar la prueba unitaria, puesto que prácticamente tenemos acabado el método de firma. Sin embargo, podemos comprobar que la prueba unitaria no funciona, debido a que obtenemos una excepcion CryptographicException: Malformed reference element. Esta excepción indica que los nodos que estamos referenciando en los métodos setXAdESReference y setKeyinforeference no se encuentran por el objeto SignedXml.
En el próximo artículo veremos como solucionar este problema. Sin embargo, ya hemos avanzado en la solución de la realización de la firma en gran medida.
f6fa996e-7682-45b7-90c7-130698c09648|1|5.0