I'm trying to pass a digital signature implementation in c# with nodejs, the implementation using native c# libraries is the only one that my provider supports, the current implementation is this.
XmlDocument SignXmlWithP12(XmlDocument xmlDoc, X509Certificate2 cert)
{
`
//Obtenemos la llave privada del certificado
var llavePrivada = cert.PrivateKey;
//Creamos un SignedXml object que contendrá el documento.
SignedXml xmlFirmado = new SignedXml(xmlDoc);
//Atachamos la llave privada al SignedXml document.
xmlFirmado.SigningKey = llavePrivada;
//Creamos una referencia del documento a ser firmado.
Reference reference = new Reference();
reference.Uri = ""; //Todo el documento
//Agregamos un enveloped transformation a la referencia.
XmlDsigEnvelopedSignatureTransform env = new XmlDsigEnvelopedSignatureTransform(true);
reference.AddTransform(env);
//Agregamos la referencia al SignedXml object.
xmlFirmado.AddReference(reference);
//Creamos un KeyInfo object.
KeyInfo keyInfo = new KeyInfo();
//Cargamos el certificado dentro de un KeyInfoX509Data object
//y luego lo añadimos al KeyInfo object.
KeyInfoX509Data clause = new KeyInfoX509Data();
//Opcionalmente se podría agregar el Subject del certificado
//clause.AddSubjectName(cert.Subject);
clause.AddCertificate(cert);
keyInfo.AddClause(clause);
//Asignamos el KeyInfo object al SignedXml object.
xmlFirmado.KeyInfo = keyInfo; //Procedemos acomputar la firma.
xmlFirmado.ComputeSignature();
//Obtenemos la representación del XML firmado y la guardamos en un XmlElement object
XmlElement xmlFirmaDigital = xmlFirmado.GetXml();
xmlDoc.DocumentElement.AppendChild(xmlDoc.ImportNode(xmlFirmaDigital, true));
return xmlDoc;
}`
Sample document and how I read it (if I put spaces, the provider rejects the digital signature)
XML DOC
`<?xml version="1.0" encoding="utf-8"?>
<SemillaModel xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<valor>QhpoXB7wtRAeL1eL9OdHPU9JE6WJksKrmWbF5+lJlcpBasy2LlUDbIeScIMUONulTcHcRLsqZUHjLtqMIydpI3j3YQ3skDyQ3yDaiNpg7ME41bbh0e+ETBKoWvhz66Ek/mfeAqLBVw41pR9Sn9b8FQ==</valor>
<fecha>2022-12-12T15:44:53.6251955-04:00</fecha>
</SemillaModel>`
Code c#
`try
{
X509Certificate2 cert = new X509Certificate2(#"x", "y", X509KeyStorageFlags.Exportable | X509KeyStorageFlags.PersistKeySet);
XmlDocument docx = new XmlDocument();
docx.Load("response_1670874293557.xml");
XmlDocument doc2 = SignXmlWithP12(docx, cert);
XmlTextWriter xmltw = new XmlTextWriter("response_1670874293557_sign_spaces_7.0.xml", new UTF8Encoding(false));
doc2.WriteTo(xmltw);
xmltw.Close();
// doc2.Save(Console.Out);
Console.WriteLine("XML file signed.");
}
catch (System.IO.FileNotFoundException)
{
}`
This works fine and my provider accepts the signatures I put in this way , however I need to pass this to nodejs .
I currently have an implementation that for some reason gives me a different digestValue
`const signed=()=>{
var sig = new SignedXml()
sig.digestAlgorithm ="http://www.w3.org/2001/04/xmlenc#sha256";
sig.signatureAlgorithm = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"
sig.keyInfoProvider = new keyProvider(public_key)
sig.canonicalizationAlgorithm = "http://www.w3.org/TR/2001/REC-xml-c14n-20010315"
sig.addReference(`//*[local-name(.)='SemillaModel']`, ["http://www.w3.org/2000/09/xmldsig#enveloped-signature"], "http://www.w3.org/2001/04/xmlenc#sha256", "", "", "", true)
sig.signingKey =signingKey;
console.log(sig.getSignatureXml());
sig.computeSignature(xml)
fs.writeFileSync(xml_sign, sig.getSignedXml())
}`
**
C#**
`string sha256(string text)
{
byte[] bytes = Encoding.Unicode.GetBytes(text);
using (var generator = new SHA256Managed())
{
byte[] hash = generator.ComputeHash(bytes);
return Convert.ToBase64String(hash);
}
}`
`const getHash = function (xml) {
console.log(xml.split('').map(char => char.charCodeAt(0)).join(' '), "end");
var shasum = crypto.createHash('sha256');
shasum.update(xml,'ucs2')
var res = shasum.digest('base64')
return res
}
`
Changing the encoding from utf-8 to ucs2 , the manual implementations without using SignedXml , are the same, but they vary with respect to if I use standard SignedXml, which I don't understand why it could be.
In node I already remove leading whitespaces, clean, also the CRLF , and nothing, to make sure of this, copy the file from c# stdin before using the signature and use the same in node , currently not sure How to proceed, I'm looking to do some kind of reverse ing to the compiled C# libraries, but I see it as quite complicated, anyone with similar experience? .
PS: I found some similar problems when they tried to switch from c# to java, however, just changing the coding was solved... something that doesn't happen in node..
I hope you can help me solve this problem that has me standing...
Related
I'm trying to sign an XML using a public key. I'm loading the certificate from a base64 string, provided by the SP. The certificate loads fine, and the public key seems to have information in it, but when I try to use it, I get the following error:
Keyset does not exist
Here's the actual code... any suggestions?? Thanks a lot!
var signingCertificateX509 = new System.Security.Cryptography.X509Certificates.X509Certificate2(Convert.FromBase64String(base64EncryptingCertificate), "password", X509KeyStorageFlags.PersistKeySet);
SignedXml signedXml = new SignedXml(doc);
signedXml.SignedInfo.CanonicalizationMethod = SignedXml.XmlDsigExcC14NTransformUrl;
signedXml.SignedInfo.SignatureMethod = "http://www.w3.org/2000/09/xmldsig#rsa-sha1";
signedXml.SigningKey = signingCertificateX509.PublicKey.Key;
I have used below methods from MSDN for signing and verifying an XML file.
The problem is that I am not able to verify the signed XML file. The SignedXML.CheckSignature() method always returns false, and not even throwing any exception on what went wrong.
Method used to sign XML file
public static void SignXMLFile(XmlDocument xmlDoc, string XmlSigFileName)
{
// Create a SignedXml object.
SignedXml signedXml = new SignedXml(xmlDoc);
string certPath = #"path to PFX file.pfx";
X509Certificate2 cert = new X509Certificate2(certPath, "password", X509KeyStorageFlags.Exportable);
var exportedKeyMaterial = cert.PrivateKey.ToXmlString(true);
var Key = new RSACryptoServiceProvider(new CspParameters(24));
Key.PersistKeyInCsp = false;
Key.FromXmlString(exportedKeyMaterial);
// Assign the key to the SignedXml object.
signedXml.SigningKey = Key;
//// Create a reference to be signed.
//Reference reference = new Reference(System.IO.File.Open(#"D:\test.docx",System.IO.FileMode.Open));
//// Add the passed URI to the reference object.
//reference.Uri = URIString;
//// Add the reference to the SignedXml object.
//signedXml.AddReference(reference);
// Create a reference to be signed.
Reference reference = new Reference();
// Add the passed URI to the reference object.
reference.Uri = "";
// Add the reference to the SignedXml object.
signedXml.AddReference(reference);
//Save the public key into the KeyValue node of the Signature
KeyInfo keyInfo = new KeyInfo();
keyInfo.AddClause(new RSAKeyValue(Key));
signedXml.KeyInfo = keyInfo;
// Compute the signature.
signedXml.ComputeSignature();
// Get the XML representation of the signature and save
// it to an XmlElement object.
XmlElement xmlDigitalSignature = signedXml.GetXml();
// Save the signed XML document to a file specified
//using the passed string.
XmlTextWriter xmltw = new XmlTextWriter(XmlSigFileName, new UTF8Encoding(false));
xmlDigitalSignature.WriteTo(xmltw);
xmltw.Close();
}
Method used to Verify signature of a XML file
// Verify the signature of an XML file and return the result.
public static Boolean VerifyXmlFile(String Name)
{
// Check the arguments.
if (Name == null)
throw new ArgumentNullException("Name");
// Create a new XML document.
XmlDocument xmlDocument = new XmlDocument();
// Format using white spaces.
xmlDocument.PreserveWhitespace = true;
// Load the passed XML file into the document.
xmlDocument.Load(Name);
// Create a new SignedXml object and pass it
// the XML document class.
SignedXml signedXml = new SignedXml(xmlDocument);
// Find the "Signature" node and create a new
// XmlNodeList object.
XmlNodeList nodeList = xmlDocument.GetElementsByTagName("Signature");
// Load the signature node.
signedXml.LoadXml((XmlElement)nodeList[0]);
signedXml.SignedInfo.CanonicalizationMethod = SignedXml.XmlDsigBase64TransformUrl;
X509Certificate2 cert = new X509Certificate2(#"path to PFX file.pfx", "password");
// Check the signature and return the result.
return signedXml.CheckSignature(cert, true);
}
I tried with all the suggesstions from stackoverflow, but no luck. Any help here is much appreciated. Thanks.
Based on data from the comments, your problem is you were trying to use an external (detached) signature, despite the code that you showed using a Uri of "", which represents "this entire document".
In the Remarks section for SignedXml on MSDN we get a couple of gems:
There is also a fourth kind of signature called an external detached signature which is when the data and signature are in separate XML documents. External detached signatures are not supported by the SignedXml class.
...
The URI attribute of the <Reference> element
...
Setting the URI attribute to an empty string indicates that the root element of the document is being signed, a form of enveloped signature.
...
Anything else is considered an external resource detached signature and is not supported by the SignedXml class.
And then there's an entire section called "The problem with external references". That section gives a link to After you apply security update 3141780, .NET Framework applications encounter exception errors or unexpected failures while processing files that contain SignedXml.
That link talks about a registry key (which I'm not going to post here, feel free to follow it) which says how to opt out of that particular portion of the MS16-035 security fixes. It also carries with it a note:
Warning Enabling this registry key could allow security vulnerabilities including Denial of Service, Distributed Reflection Denial of Service, Information Disclosure, Signature Bypass, and Remote Code Execution.
So, if you need to do it... be careful.
We are signing a xml elements using SignedXml class. The requirement is add signature node in Ws Security to communicate with the service.
We could able to sign the elements and verified those. In this case the xml Sample code is as below
X509Certificate2 certificate = new X509Certificate2(CertPath, CertPassword);
// Create a new XML document.
XmlDocument doc = new XmlDocument();
// Format the document to ignore white spaces.
doc.PreserveWhitespace = false;
// Load the passed XML file using it's name.
doc.Load(new XmlTextReader(FileName));
SignedXml signedXml = new SignedXml(doc);
signedXml.SigningKey = certificate.PrivateKey;
// Create a reference to be signed.
Reference reference = new Reference();
reference.Uri = "#" + elementID;
// Add an enveloped transformation to the reference.
XmlDsigEnvelopedSignatureTransform envTransform = new XmlDsigEnvelopedSignatureTransform();
reference.AddTransform(envTransform);
// Add the reference to the SignedXml object.
signedXml.AddReference(reference);
// Create a new KeyInfo object.
KeyInfo keyInfo = new KeyInfo();
// Load the certificate into a KeyInfoX509Data object
// and add it to the KeyInfo object.
keyInfo.AddClause(new KeyInfoX509Data(certificate));
// Add the KeyInfo object to the SignedXml object.
signedXml.KeyInfo = keyInfo;
// Compute the signature.
signedXml.ComputeSignature();
// Get the XML representation of the signature and save
// it to an XmlElement object.
XmlElement xmlDigitalSignature = signedXml.GetXml();
by using this Signature element generated as <signature> .... </signature> . but we want to generate it as <ds:signature> .... </ds:signature>
Tried to set the prefix explicitly but then signature is not validated after.
Could you please guide how can we achieve this?
As this is a generic enough question, this post might be helpful to someone looking for the same information.
For the specific project I'm working on, IRS A2A submission, it is what I used when thinking that I needed to add the prefixes to the Signature element. However, in discussion with some other SO users, I have abandoned the idea of adding the prefixes to the Signature element as it is not actually necessary.
I'm trying to use SignedXml in .Net with ECDsa (ECDsaCng) and SHA256 and I'm getting an "CryptographicException" with message "Failed to create signing key.". Does someone knows how to achieve that goal or if I should know relevant information about .net support for ecdsa? thank you in advance.
public XmlDocument SignXml(XmlDocument xmlDoc)
{
CngKey cngKey = CngKey.Create(CngAlgorithm.ECDsaP256);
ecDsaCng = new ECDsaCng(cngKey);
ecDsaCng.HashAlgorithm = CngAlgorithm.ECDsaP256;
ecDsaCng.KeySize = 256;
xmlDoc.PreserveWhitespace = true;
SignedXml signedXml = new SignedXml(xmlDoc);
signedXml.SigningKey = ecDsaCng;
Reference reference = new Reference();
reference.Uri = "";
XmlDsigEnvelopedSignatureTransform env = new XmlDsigEnvelopedSignatureTransform();
reference.AddTransform(env);
signedXml.AddReference(reference);
signedXml.ComputeSignature();
XmlElement xmlDigitalSignature = signedXml.GetXml();
xmlDoc.DocumentElement.AppendChild(xmlDoc.ImportNode(xmlDigitalSignature, true));
return xmlDoc;
}
Error appears in "signedXml.ComputeSignature();" method call.
Have a look in the assembly
[System.Security, Version=4.0.0.0, Culture=neutral,
PublicKeyToken=b03f5f7f11d50a3a]
in the method
public void ComputeSignature()
Only RSA and DSA keys are accepted. It is a shame that it isn't specified somewhere on the MSDN. You will have to pass on the SignedXml class.
I'm trying to sign Xml (actually SOAP xml) in C# using the SignedXml class, the signing stage passes successfully, but when I try to verify the signature it tells me that it is not valid.
The only change I have done from the example in MSDN that I used XmlDsigExcC14NTransform instead of the transform XmlDsigEnvelopedSignatureTransform.
If I use XmlDsigEnvelopedSignatureTransform I will get a valid signature.
Here is my signing code:
private static XmlDocument SignXml(XmlDocument doc)
{
SignedXml signedXml = new SignedXml(doc);
signedXml.SigningKey = Certificate.PrivateKey;
Reference reference = new Reference();
reference.Uri = "";
XmlDsigEnvelopedSignatureTransform env = new XmlDsigEnvelopedSignatureTransform();
//XmlDsigExcC14NTransform env = new XmlDsigExcC14NTransform();
reference.AddTransform(env);
signedXml.AddReference(reference);
signedXml.ComputeSignature();
XmlElement signature = signedXml.GetXml();
doc.DocumentElement.AppendChild(signature);
doc.Save(SignedXmlPath);
return doc;
}
The above code will give me a valid signature, but if I use
XmlDsigExcC14NTransform env = new XmlDsigExcC14NTransform();
instead of
XmlDsigEnvelopedSignatureTransform env = new XmlDsigEnvelopedSignatureTransform();
I will get an invalid signature.
Here is my verification code:
private static bool Verify(XmlDocument doc)
{
SignedXml signedDoc = new SignedXml(doc);
XmlNodeList nodeList = doc.GetElementsByTagName(Constants.SignatureElement);
signedDoc.LoadXml((XmlElement)nodeList[0]);
return signedDoc.CheckSignature((RSA)Certificate.PublicKey.Key);
}
Can anyone tell me how can I sign with transform algorithm of http://www.w3.org/2001/10/xml-exc-c14n#
Thanks in advance.
You are gonna need the XmlDsigEnvelopedSignatureTransform in your case because you are adding the signature inside the element you are signing.
XmlDsigEnvelopedSignatureTransform will tell the SignedXml class to remove the signature from the signature node itself before testing it's validity. This is needed because you added that element after calculating the signature.
You can add more then one transform by calling the AddTransform again like this:
XmlDsigEnvelopedSignatureTransform env = new XmlDsigEnvelopedSignatureTransform();
XmlDsigExcC14NTransform c14n = new XmlDsigExcC14NTransform();
reference.AddTransform(env);
reference.AddTransform(c14n);
However i think what you actually want to do instead of my example above is set the CanonicalizationMethod to c14n:
signedXml.SignedInfo.CanonicalizationMethod = "http://www.w3.org/2001/10/xml-exc-c14n#";
- or -
signedXml.SignedInfo.CanonicalizationMethod = SignedXml.XmlDsigExcC14NTransformUrl;