0

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?

Lord of Scripts
  • 3,579
  • 5
  • 41
  • 62
  • 1
    I just tried your code where the only modification was building RSA objects from (fixed) RSAParameters and using a fixed input string, and it worked fine; both with the exclusive c14n and the default one. So you might want to check that you're really using the same cert on both sides. For example, take the RSA object, call ExportParameters(false) and print the Modulus value. (A lot of cleanup could apply here, but my testing shows your code is functional, suggesting a data problem). Failing that, give a sample input string from your serializer. – bartonjs Sep 07 '16 at 01:38
  • @bartonjs I'll be damned! I did as you suggested and the modulus were NOT the same! strange because I generated the .CER from the certificate in the store. However, what I did not was to switch to .NET 3.5 in the SelfCert PluralSight utility (don't know if that does something) and then rather than using it to save the certificate on the store I used the saved PFX file to manually install it on the store. Then I went to the store and exported it. The problem was solved that way. Now it verifies properly. – Lord of Scripts Sep 08 '16 at 19:00
  • 1
    glad I could help :) – bartonjs Sep 08 '16 at 20:20

1 Answers1

0

Thanks to the great hint by @bartonjs I could verify the modulus found in the sign and verify certificates were not the same. As I mentioned I used the PluralSight free SelfCert GUI both when the problem ocurred and in the new (fixed) situation.

No changes were made to the code, the problem lied on the certificate even though the .CER file used for verification was generated out of the cert that was stored.

The difference was operational. If I let SelfCert.exe to install the certificate on the store, the verification method would fail. If on the other hand, after generating the .PFX file I did not use the "Save" button on their GUI but instead used the generated .PFX file to manually install the certificate on the store with its private key and only then used the stored certificate to export it as a .CER file. Now the modulus are the same and sign/verify process goes smoothly without failing.

Lord of Scripts
  • 3,579
  • 5
  • 41
  • 62