I am trying to verify a signed XML document following Microsoft directives but CheckSignature always returns false. I am using .NET 4.5. Originally I was not using the C14 transform but that did not work, it was suggested as a solution in .NET 4.5 but does not work. Do notice as well that preserve whitespace is true for both signing and verification.
In the code snippets I have removed the error checking code for simplicity. And yes, I checked similar questions but found no answer.
Certificates (.PFX and .CER)
Signing and verification is done by means of a certificate on a file (not the store):
- Used PluralSight SelfCert self-signed certificate tool
- Created a personal self-signed certificate for Digital Signing with an exportable private key protected with a password. I can see the certificate in My personal store using certmgr.msc
- I saved by signing certificate (.PFX) that contains both public and private keys
- I used certmgr.msc to export the certificate without private key, so just the public key. This is for verifcation only and was saved to a .CER file
So far so good, the certificates are good. The personal store shows my signing certificate with both the certificate and key icons.
Signing XML Document
Now For signing XML I am using this code:
static void SignXML(XmlDocument xmlDoc, RSA Key)
{
SignedXml signedXml = new SignedXml(xmlDoc);
if (Properties.Settings.Default.UseC14Transform)
{
// http://stackoverflow.com/questions/13632630/signedxml-checksignature-fails-in-net-4-but-it-works-in-net-3-5-3-or-2
signedXml.SignedInfo.CanonicalizationMethod = SignedXml.XmlDsigExcC14NTransformUrl;
}
// Add the key to the SignedXml document.
signedXml.SigningKey = Key;
// Create a reference to be signed.
Reference reference = new Reference();
reference.Uri = "";
if (Properties.Settings.Default.UseC14Transform)
{
reference.AddTransform(new XmlDsigExcC14NTransform()); // required to match doc
XmlDsigEnvelopedSignatureTransform env = new XmlDsigEnvelopedSignatureTransform();
reference.AddTransform(env);
}
else
{
// Add an enveloped transformation to the reference.
XmlDsigEnvelopedSignatureTransform env = new XmlDsigEnvelopedSignatureTransform();
reference.AddTransform(env);
}
// Add the reference to the SignedXml object.
signedXml.AddReference(reference);
// Compute the signature.
signedXml.ComputeSignature();
// Get the XML representation of the signature and save
// it to an XmlElement object.
XmlElement xmlDigitalSignature = signedXml.GetXml();
// Append the element to the XML document.
xmlDoc.DocumentElement.AppendChild(xmlDoc.ImportNode(xmlDigitalSignature, true));
}
The signing code is called like this:
XmlDocument doc = new XmlDocument();
doc.PreserveWhitespace = true;
using (StringWriter _writer = new StringWriter())
{
XmlSerializer serializer = new XmlSerializer(typeof(Models.Protected), new Type[] { param1.GetType() });
serializer.Serialize(_writer, param1);
doc.LoadXml(_writer.ToString());
}
// get RSA key from certificate (.PFX file)
X509Certificate2 cert = new X509Certificate2(certPrivateKeyFilePath, certFilePwd);
RSACryptoServiceProvider rsaKey = (RSACryptoServiceProvider)cert.PrivateKey;
SignXML(doc, rsaKey);
return Convert.ToBase64String(Encoding.UTF8.GetBytes(doc.OuterXml));
Verification of XML Signature
My verification method looks like this:
static Boolean VerifyXml(XmlDocument Doc, X509Certificate2 cert)
{
RSACryptoServiceProvider rsaKey = (RSACryptoServiceProvider)cert.PublicKey.Key;
SignedXml signedXml = new SignedXml(Doc);
XmlNodeList nodeList = Doc.GetElementsByTagName("Signature");
signedXml.LoadXml((XmlElement)nodeList[0]);
return signedXml.CheckSignature(rsaKey);
}
which is in turn invoked like this:
Models.Protected mdl = null;
try
{ // The certificate is in a .CER file, only has PUBLIC key
X509Certificate2 cert = new X509Certificate2(certPubKeyFilePath);
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.PreserveWhitespace = true;
xmlDoc.LoadXml(DocEncoding.GetString(Convert.FromBase64String(b64String)));
if (VerifyXml(xmlDoc, cert)
{
XmlNodeList nodeList = xmlDoc.GetElementsByTagName("Signature");
xmlDoc.DocumentElement.RemoveChild(nodeList[0]);
XmlSerializer _serializer = new XmlSerializer(typeof(Models.Protected), new Type[] { typeof(Models.Protected) });
using (StringReader _reader = new StringReader(_safeXML))
{
_safe = (Models.Protected)_serializer.Deserialize(_reader);
}
}
}
catch { /* something here */ }
return _safe;
}
Miscellaneous
The object that is being signed is a Models.Protected class which is serializable. I have verified that it serializes properly so the problem does not lie there.
The signing process produces a valid XL file with the expected XML signature and this is then converted to a Base64 string.
The verifiation process reads a Base64 string which I have verified that is the same as what it was generated. This same B64 string that was read when decoded produces a signed XML document that is exactly the same as the one produced when signing was complete.
The MD5 hashe of both the signed XML that was produced is the same as the MD5 hash of the signed XML that was obtained prior to the verification. Therefore they are absolutely equal and so are their signatures.
It is thus beyond my understanding why on earth CheckSignature returns false even though the content is identical and that the certs were correctly produced (.PFX and .CER). No exceptions are thrown anywhere, it simply returns false.
Can anyone spot what I am doing wrong?