none
Conceptual approach question: Sign XML Documents with Digital Signatures and verify on a remote client. RRS feed

  • Question

  • Environment: Windows 7 (64-bit), VS 2008 SP1, WCF, ASP.NET/Azure, .NET 3.5 Server, .NET 3.0 WPF client.

    Background: I want to distribute a WPF 3.0 desktop application. The application is perishable, and will expire after 60 days. On startup, the application will check to see if it is still "fresh". I thought a simple way to do that is to have it verify, and then read expiry information from, a signed XML document.

    I have seen <http://msdn.microsoft.com/en-us/library/ms229745(v=VS.100).aspx> and <http://msdn.microsoft.com/en-us/library/ms229950(v=VS.100).aspx> on signing and verifying Digital Signatures of XML Documents. Great stuff! The example works if I sign & verify on the same machine.

    I am just missing one piece of the puzzle. I want to sign the document on one machine, and verify the document in a completely different machine (where the WPF application is installed). Of course the installer will copy the signed XML document to a well-known location on the other machine.

    I believe I will have to send Public Key information to the verification location, but am not sure how to package/transmit/invoke at the verification location.

    I see methods such as rsacsp.ToXmlString(false); // or true to include Private Key info

    or

    RSAParameters rsacspInfo = rsacsp.ExportParameters(false); // or true to include Private Key info

    Can someone fill in the conceptual gap for me? (current code outline follows)

     

    Source machine –

    CspParameters cspParams = new CspParameters();

    cspParams.KeyContainerName = "XML_DSIG_RSA_KEY";

    RSACryptoServiceProvider rsacsp = new RSACryptoServiceProvider(cspParams);

     …

    sign(xmlDoc, rsacsp);

     … Save XML document with expiry information and install on target machine.

     

    Target machine –

    CspParameters cspParams = new CspParameters();

    cspParams.KeyContainerName = "XML_DSIG_RSA_KEY";

    RSACryptoServiceProvider rsacsp = new RSACryptoServiceProvider(cspParams);

     … load document …

    SignedXml signedXml = new SignedXml(xmlDoc);

    XmlNodeList nodeList = xmlDoc.GetElementsByTagName("Signature");

    signedXml.LoadXml((XmlElement)nodeList[0]); // load Signature node

    bool success = signedXml.CheckSignature(rsacsp);

    Saturday, April 24, 2010 9:17 PM

Answers

  •  

    I cannot find a way to export/import only public key from/into a key container, I have to supply a key pair to key container.

     

    I did some refine basing on your code, and it works on my machines (Win7 + Win7 64-bit), here is what I did:

    1. Generate two key files, one is rsaKeyPair.xml which contains both private and public key, another is rsaPublicKey.xml which only contains public key ( this key is the public key which included in rsaKeyPair.xml).

     

    2. Use following methods to encrypt app.xml to app.encrypt.xml.

     

    public static void SignXml(string fileToSign, string outputFile, string rsaKeyPairFile) 
        {
          RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
          rsa.FromXmlString(File.ReadAllText(rsaKeyPairFile));
    
          XmlDocument xmlDoc = new XmlDocument();
          xmlDoc.PreserveWhitespace = true;
          xmlDoc.Load(fileToSign);
    
          SignedXml signedXml = new SignedXml(xmlDoc);
          signedXml.SigningKey = rsa;
          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));
          xmlDoc.Save(outputFile);
        }
    
    public static void Test
    {
            string rsaKeyPairFile = Environment.CurrentDirectory + "\\rsaKeyPair.xml";
            
            string fileToSign = Environment.CurrentDirectory + "\\app.xml";
            string outputFile = Environment.CurrentDirectory + "\\app.encrypted.xml";
    
            CaseInOne.Crypt.XMLEncryptionHelper.SignXml(fileToSign, outputFile, rsaKeyPairFile);
    }

    3. Copy rsaPublicKey.xml and app.encrypt.xml to another machine, use following code snippet to verify the app.encrpt.xml:

     

    public static bool VerifyXml(string fileToVerify, string rsaPublicKeyFile) 
        {
          RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
          rsa.FromXmlString(File.ReadAllText(rsaPublicKeyFile));
    
          XmlDocument xmlDoc = new XmlDocument();
          xmlDoc.PreserveWhitespace = true;
          xmlDoc.Load(fileToVerify);
    
          SignedXml signedXml = new SignedXml(xmlDoc);
          XmlNodeList nodeList = xmlDoc.GetElementsByTagName("Signature");
          signedXml.LoadXml((XmlElement)nodeList[0]); 
    
          return signedXml.CheckSignature(rsa);
        }
    
    public static void Test()
    {
            string outputFile = Environment.CurrentDirectory + "\\app.encrypted.xml";
            string rsaPublicKeyFile = Environment.CurrentDirectory + "\\rsaPublicKey.xml";
            bool result = CaseInOne.Crypt.XMLEncryptionHelper.VerifyXml(outputFile, rsaPublicKeyFile);
    }

    Sincerely,
    Eric
    MSDN Subscriber Support in Forum
    If you have any feedback of our support, please contact msdnmg@microsoft.com.
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.
    Welcome to the All-In-One Code Framework! If you have any feedback, please tell us.
    • Marked as answer by WindowsRich Tuesday, April 27, 2010 4:04 PM
    Tuesday, April 27, 2010 5:26 AM

All replies

  •  

    Hi,

    From my understanding, you are looking for a way to send the public key to your clients, am I right?

    If so, why not include the public key in the package, and import the public key to client machine's certificate store in deployment phase.


    Sincerely,
    Eric
    MSDN Subscriber Support in Forum
    If you have any feedback of our support, please contact msdnmg@microsoft.com.
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.
    Welcome to the All-In-One Code Framework! If you have any feedback, please tell us.
    Monday, April 26, 2010 9:35 AM
  • eryang,

    Thanks for the response, but I have not succeeded in exporting/importing the Public key as a way to verify my signed XML file. This post <http://stackoverflow.com/questions/827518/rsa-encryption-public-key-not-returned-from-container> implies there is a problem trying to do this as well.

    However, I am happy to be proven wrong :)

    I have tried the following without success, perhaps you can identify the error in my logic (I have simplied the code by removing error handling, comments, and other features, but you will understand what I have tried so far):

    First, I create a RSACryptoServiceProvider, write the public key to a file, and sign the XML document.

    class SignXml {
            private string xmlFilepath = @"c\:createfiles";
            private string xmlFileName = @"\license.xml";
            private string publicKeyFile = @"\publickey.txt";

            public SignXml() {
                CspParameters cspParams = new CspParameters();
                cspParams.KeyContainerName = "KEYCONTAINER_ORIGIN";
                RSACryptoServiceProvider rsacsp = new RSACryptoServiceProvider(cspParams);
                rsacsp.PersistKeyInCsp = true;
                System.IO.StreamWriter sw = new System.IO.StreamWriter(xmlFilepath + publicKeyFile, false);
                sw.Write(rsacsp.ToXmlString(false));
                sw.Close();
                XmlDocument xmlDoc = new XmlDocument();
                xmlDoc.PreserveWhitespace = true;
                xmlDoc.Load(xmlFilepath + xmlFileName);
                sign(xmlDoc, rsacsp);
                xmlDoc.Save(xmlFilepath + xmlFileName);
            }

            private void sign(XmlDocument xmlDoc, RSA key) {
                SignedXml signedXml = new SignedXml(xmlDoc);
                signedXml.SigningKey = key;
                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));
            }
    }


    Second, I transfer the file with the public key and the signed XML document to a remote machine.

    Third, I create a RSACryptoServiceProvider, read the public key from the file, and verify the XML document. Unfortunately, the verification always fails.

    class VerifyXml
        {
            private string xmlFilepath = @"c\:installfiles";
            private string xmlFileName = @"\license.xml";
            private string publicKeyFile = @"\publickey.txt";

            public VerifyXml() {
                CspParameters cspParams = new CspParameters();
                cspParams.KeyContainerName = "KEYCONTAINER_DESTINATION";
                RSACryptoServiceProvider rsacsp = new RSACryptoServiceProvider(cspParams);
                StreamReader sr = new StreamReader(xmlFilepath + publicKeyFile);
                string keytxt = sr.ReadToEnd();
                rsacsp.FromXmlString(keytxt);
                sr.Close();
                rsacsp.PersistKeyInCsp = true;
                XmlDocument xmlDoc = new XmlDocument();
                xmlDoc.PreserveWhitespace = true;
                xmlDoc.Load(xmlFilepath + xmlFileName);
                bool success = verify(xmlDoc, rsacsp);
            }

            private bool verify(XmlDocument xmlDoc, RSA key) {
                SignedXml signedXml = new SignedXml(xmlDoc);
                XmlNodeList nodeList = xmlDoc.GetElementsByTagName("Signature"); // find "Signature" node
                signedXml.LoadXml((XmlElement)nodeList[0]); // load Signature node
                return signedXml.CheckSignature(key);
            }
    }

    What am I doing wrong?

    Monday, April 26, 2010 10:14 PM
  •  

    I cannot find a way to export/import only public key from/into a key container, I have to supply a key pair to key container.

     

    I did some refine basing on your code, and it works on my machines (Win7 + Win7 64-bit), here is what I did:

    1. Generate two key files, one is rsaKeyPair.xml which contains both private and public key, another is rsaPublicKey.xml which only contains public key ( this key is the public key which included in rsaKeyPair.xml).

     

    2. Use following methods to encrypt app.xml to app.encrypt.xml.

     

    public static void SignXml(string fileToSign, string outputFile, string rsaKeyPairFile) 
        {
          RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
          rsa.FromXmlString(File.ReadAllText(rsaKeyPairFile));
    
          XmlDocument xmlDoc = new XmlDocument();
          xmlDoc.PreserveWhitespace = true;
          xmlDoc.Load(fileToSign);
    
          SignedXml signedXml = new SignedXml(xmlDoc);
          signedXml.SigningKey = rsa;
          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));
          xmlDoc.Save(outputFile);
        }
    
    public static void Test
    {
            string rsaKeyPairFile = Environment.CurrentDirectory + "\\rsaKeyPair.xml";
            
            string fileToSign = Environment.CurrentDirectory + "\\app.xml";
            string outputFile = Environment.CurrentDirectory + "\\app.encrypted.xml";
    
            CaseInOne.Crypt.XMLEncryptionHelper.SignXml(fileToSign, outputFile, rsaKeyPairFile);
    }

    3. Copy rsaPublicKey.xml and app.encrypt.xml to another machine, use following code snippet to verify the app.encrpt.xml:

     

    public static bool VerifyXml(string fileToVerify, string rsaPublicKeyFile) 
        {
          RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
          rsa.FromXmlString(File.ReadAllText(rsaPublicKeyFile));
    
          XmlDocument xmlDoc = new XmlDocument();
          xmlDoc.PreserveWhitespace = true;
          xmlDoc.Load(fileToVerify);
    
          SignedXml signedXml = new SignedXml(xmlDoc);
          XmlNodeList nodeList = xmlDoc.GetElementsByTagName("Signature");
          signedXml.LoadXml((XmlElement)nodeList[0]); 
    
          return signedXml.CheckSignature(rsa);
        }
    
    public static void Test()
    {
            string outputFile = Environment.CurrentDirectory + "\\app.encrypted.xml";
            string rsaPublicKeyFile = Environment.CurrentDirectory + "\\rsaPublicKey.xml";
            bool result = CaseInOne.Crypt.XMLEncryptionHelper.VerifyXml(outputFile, rsaPublicKeyFile);
    }

    Sincerely,
    Eric
    MSDN Subscriber Support in Forum
    If you have any feedback of our support, please contact msdnmg@microsoft.com.
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.
    Welcome to the All-In-One Code Framework! If you have any feedback, please tell us.
    • Marked as answer by WindowsRich Tuesday, April 27, 2010 4:04 PM
    Tuesday, April 27, 2010 5:26 AM
  • Thank you! Worked well for me.

    Just for others reading this solution, here is a rough idea for the code needed for step 1 -

    string xmlFilepath = Environment.CurrentDirectory;

    private string keyPairFile = @"\rsaKeyPair.xml";
    private string publicKeyFile = @"\rsaKeyPublic.xml";

    RSACryptoServiceProvider rsacsp = new RSACryptoServiceProvider(); // create two key files
    System.IO.StreamWriter kpsw = new System.IO.StreamWriter(xmlFilepath + keyPairFile, false);
    kpsw.Write(rsacsp.ToXmlString(true)); //export key pair info to file
    kpsw.Close();
    System.IO.StreamWriter pksw = new System.IO.StreamWriter(xmlFilepath + publicKeyFile, false);
    pksw.Write(rsacsp.ToXmlString(false)); //export Public key info
    pksw.Close();

    Tuesday, April 27, 2010 4:12 PM