Search code examples
c#hashcryptographyxml-signaturexml-dsig

Calculating SignatureValue from SignedInfo using RSACryptoServiceProvider


I am new to XML digital signing topic. After reading some tutorials, I decided to do some hands-on activity and tried to follow some examples to improve my understanding. There's one particular point that I need help with.

Here's the example I am trying:

https://learn.microsoft.com/en-us/dotnet/standard/security/how-to-sign-xml-documents-with-digital-signatures https://learn.microsoft.com/en-us/dotnet/standard/security/how-to-verify-the-digital-signatures-of-xml-documents

The data to be signed is simple (copy/paste from the site yielded TAB characters so I turned all the TABs into single SPACEs before saving the file to disk):

<root>  
 <creditcard>  
  <number>19834209</number>  
  <expiry>02/02/2002</expiry>  
 </creditcard>  
</root>

When I run the example code with this data, the following Signature element is generated and added to the file within the root element:

<Signature xmlns="http://www.w3.org/2000/09/xmldsig#"><SignedInfo><CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315" /><SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256" /><Reference URI=""><Transforms><Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" /></Transforms><DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256" /><DigestValue>AjI/9vkjyZWM9noiF0J0RRZEUAjBil96ETvT9Cl85rI=</DigestValue></Reference></SignedInfo><SignatureValue>YY5B15W2W9TyoV4HjZTh554Z/yh06iz7+yKM/AgsdzU6QvakH54rPJZ9rQkIrLehxJlX76xXgp9Pe+ougMA6QSj7NvqRBXoxgQcpf1W/m4cfCzTd7iT7A93xhqCdU1F4AohvJZRqwbeApOqxjuXmcF9kqLFDcSTuRZSgIPJdSS8=</SignatureValue></Signature>

I manually canonicalized the input and wrote a bit of code and verified that the SHA256 digest of canonicalized input indeed matches the DigestValue in the Signature (i.e. my understanding of how things work under the covers, at least regarding canonicalization, is correct so far). However, my attempts at verifying the signature value has failed so far. No matter what I do, I can't get the signature value that the API yields. Of course, the verification part of the example (using the API) works and indicates the signature is valid.

Here's the code for "manual" verification:

CspParameters cspParams = new CspParameters();
cspParams.KeyContainerName = "XML_DSIG_RSA_KEY";

RSACryptoServiceProvider rsaKey = new RSACryptoServiceProvider(cspParams);

byte[] originalData = File.ReadAllBytes("signedinfo.xml");
byte[] signedBytes;

try
{
    SHA256 mySHA256 = SHA256.Create();
    byte[] digest=mySHA256.ComputeHash(originalData);
    signedBytes = rsaKey.SignHash(digest, CryptoConfig.MapNameToOID("SHA256"));
    Console.WriteLine(System.Convert.ToBase64String(signedBytes));
}
catch (CryptographicException e)
{
    Console.WriteLine(e.Message);
}

SignedInfo.xml is as follows:

<SignedInfo><CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315" /><SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256" /><Reference URI=""><Transforms><Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" /></Transforms><DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256" /><DigestValue>AjI/9vkjyZWM9noiF0J0RRZEUAjBil96ETvT9Cl85rI=</DigestValue></Reference></SignedInfo>

I found a guide at https://www.di-mgt.com.au/xmldsig2.html that mentions propagating of namespaces during canonicalization of SignedInfo, so I tried the following SignedInfo.xml as well:

<SignedInfo xmlns="http://www.w3.org/2000/09/xmldsig#><CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315" /><SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256" /><Reference URI=""><Transforms><Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" /></Transforms><DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256" /><DigestValue>AjI/9vkjyZWM9noiF0J0RRZEUAjBil96ETvT9Cl85rI=</DigestValue></Reference></SignedInfo>

This still doesn't yield the same signature as the API in the example does.

I would appreciate any help in figuring out what I am doing wrong.


Solution

  • It was my mistake in not reading the guide completely. I forgot to expand the elements from the form <tag /> to the form <tag></tag>. The correct canonical SignedInfo therefore is

    <SignedInfo><CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"></CanonicalizationMethod><SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"></SignatureMethod><Reference URI=""><Transforms><Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"></Transform></Transforms><DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"></DigestMethod><DigestValue>AjI/9vkjyZWM9noiF0J0RRZEUAjBil96ETvT9Cl85rI=</DigestValue></Reference></SignedInfo>
    

    And with this SignedInfo, I can "manually" validate the signature.

    While I am at it, let me include one more bit of information that might help others who see this question: There's a note about how .Net's implementation of the inclusive c14n algorithm (Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315") is different from the spec and from other implementations. This difference has implications on cross-platform verification. In my learning this did not affect me but in case one needs to do cross-platform verification, this is one issue to pay attention to.