locked
SignedXml and detached signatures RRS feed

  • Question

  • Hi All,

    I am trying to use the SignedXml class to create and store a digital signature for various documents.  The documents can be rather large, so rather than storing the digital signature in an enveloped XML file, I am using detached signatures.  The hope is to store the signatures in a database.  The problem I can't seem to get around is it appears that for the detached signatures to work properly, I have to provide a URI for the document whose signature is to be created/verified.  I can't seem to find a way to simply supply the document explicitly (i.e., using a Stream). 

    This is the basic code I am trying for signing:

    private static Stream CreateSignature(Stream sourceDocument, X509Certificate2 certificate)
    {
      SignedXml signedXml = new SignedXml();
      signedXml.SigningKey = certificate.PrivateKey;

      Reference reference = new Reference(sourceDocument);
      signedXml.ad.AddReference(reference);

      KeyInfo keyInfo = new KeyInfo();
      KeyInfoX509Data certificateData = new KeyInfoX509Data(certificate);
      keyInfo.AddClause(certificateData);
      signedXml.KeyInfo = keyInfo;

      signedXml.ComputeSignature();
      XmlElement signatureXml = signedXml.GetXml();

      MemoryStream signature = new MemoryStream();
      using (XmlTextWriter signatureWriter = new XmlTextWriter(signature, Encoding.UTF8))
      {
        signatureXml.WriteTo(signatureWriter);
        signatureWriter.Flush();
      }
      return signature;
    }

    And this is the basic code I am using to verify the signature:

    public static bool VerifySignature(Stream document, Stream signature)
    {
      XmlDocument signatureDocument = new XmlDocument();
      signatureDocument.Load(signature);

      XmlNamespaceManager nsMgr = new XmlNamespaceManager(signatureDocument.NameTable);
      nsMgr.AddNamespace("ds", "http://www.w3.org/2000/09/xmldsig#");
      XmlNode signatureNode = signatureDocument.SelectSingleNode("//ds:Signature", nsMgr);

      SignedXml signatureXml = new SignedXml();
      signatureXml.LoadXml(signatureNode);
      Reference documentReference = new Reference(document);
      signatureXml.AddReference(documentReference);
      return signatureXml.CheckSignature();
    }

    Using this code, the signature XML is created and appears to be OK.  However, when I try to verify the signature, I get this exception:

    System.NullReferenceException: Object reference not set to an instance of an object.
       at System.Security.Cryptography.Xml.Reference.GetXml(XmlDocument document)
       at System.Security.Cryptography.Xml.SignedInfo.GetXml(XmlDocument document)
       at System.Security.Cryptography.Xml.SignedXml.GetC14NDigest(HashAlgorithm hash)
       at System.Security.Cryptography.Xml.SignedXml.CheckSignedInfo(AsymmetricAlgorithm key)
       at System.Security.Cryptography.Xml.SignedXml.CheckSignature()
       at XMS.DigitialSignature.DetachedSignature.VerifySignature(Stream document, Stream signature)...

    Can anyone verify if the approach I am trying to take is supported?  If I want to use the SignedXml classes, do I have to reference the document using a URI?

    Thanks in advance

    Tuesday, March 16, 2010 12:34 PM

Answers

  • Hi, Bimish:

       After some research, I came up with the following solution.

        For the URI part, just use something like this: string resourceToSign = @"file://d:\test.docx";

         The complete code sample below demonstrates how to create a detatched XMLSignature for a Word document and then verify it. The public key is stored within the XML signature.

        Before running this code, make sure there's test.docx file loaced at the d:\.

    //
    // This example signs a file specified by a URI 
    // using a detached signature. It then verifies  
    // the signed XML.
    //
    
    using System;
    using System.Security.Cryptography;
    using System.Security.Cryptography.Xml;
    using System.Text;
    using System.Xml;
    
    
    
    class XMLDSIGDetached
    {
    
        [STAThread]
        static void Main(string[] args)
        {
            // The URI to sign.
            string resourceToSign = @"file://d:\test.docx";
    
            // The name of the file to which to save the XML signature.
            string XmlFileName = "xmldsig.xml";
    
            try
            {
    
                // Generate a signing key.
                RSACryptoServiceProvider Key = new RSACryptoServiceProvider();
    
                Console.WriteLine("Signing: {0}", resourceToSign);
    
                // Sign the detached resourceand save the signature in an XML file.
                SignDetachedResource(resourceToSign, XmlFileName, Key);
    
                Console.WriteLine("XML Signature was succesfully computed and saved to {0}.", XmlFileName);
    
                Console.Read();
    
                // Verify the signature of the signed XML.
                Console.WriteLine("Verifying signature...");
    
                //Verify the XML signature in the XML file against the key.
                //bool result = VerifyDetachedSignatureWithRSAPublicKey(XmlFileName, Key);
                bool result = VerifyDetachedSignature(XmlFileName);
    
                // Display the results of the signature verification to 
                // the console.
                if (result)
                {
                    Console.WriteLine("The XML signature is valid.");
                }
                else
                {
                    Console.WriteLine("The XML signature is not valid.");
                }
            }
            catch (CryptographicException e)
            {
                Console.WriteLine(e.Message);
    
            }
    
        }
    
    
        // Sign an XML file and save the signature in a new file. This method does not  
        // save the public key within the XML file.  This file cannot be verified unless  
        // the verifying code has the key with which it was signed.
        public static void SignDetachedResource(string URIString, string XmlSigFileName, RSA Key)
        {
            // Create a SignedXml object.
            SignedXml signedXml = new SignedXml();
    
            // Assign the key to the SignedXml object.
            signedXml.SigningKey = Key;
    
            // Create a reference to be signed.
            Reference reference = new Reference();
            
            // Add the passed URI to the reference object.
            reference.Uri = URIString;
    
            // 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();
    
        }
    
        // Verify the signature of an XML file against an asymetric 
        // algorithm and return the result.
        public static Boolean VerifyDetachedSignatureWithRSAPublicKey(string XmlSigFileName, RSA Key)
        {
            // Create a new XML document.
            XmlDocument xmlDocument = new XmlDocument();
    
            // Load the passedXML file into the document.
            xmlDocument.Load(XmlSigFileName);
    
            // Create a new SignedXml object.
            SignedXml signedXml = new SignedXml();
    
            // Find the "Signature" node and create a new XmlNodeList object.
            XmlNodeList nodeList = xmlDocument.GetElementsByTagName("Signature");
    
            // Load the signature node.
            signedXml.LoadXml((XmlElement)nodeList[0]);
    
            // Check the signature against the passed asymetric key
            // and return the result.
            return signedXml.CheckSignature(Key);
        }
    
        //Verify the siganature of an XML file
        public static Boolean VerifyDetachedSignature(String XmlSigFileName)
        {
            // Create a new XML document.
            XmlDocument xmlDocument = new XmlDocument();
    
            // Load the passedXML file into the document.
            xmlDocument.Load(XmlSigFileName);
    
            // Find the "KeyValue" node,
            // get the public key and use the key to initialize
            // a RSACryptoServiceProvider object.
            XmlNodeList nodeList = xmlDocument.GetElementsByTagName("KeyValue");
            RSACryptoServiceProvider Key = new RSACryptoServiceProvider();
            Key.FromXmlString(nodeList[0].InnerXml);
    
            // Check the signature against the RSACryptoServiceProvider object
            // and return the result.
            return VerifyDetachedSignatureWithRSAPublicKey(XmlSigFileName, Key);
        }
    }
    

        Some additional references:

        1. SignedXml Class

        2. XML Digital Signature

        3. XML Signature

     


    Please mark the right answer at right time.
    Thanks,
    Sam

    • Marked as answer by SamAgain Monday, March 29, 2010 8:57 AM
    • Edited by SamAgain Wednesday, March 31, 2010 2:36 AM refine
    Monday, March 29, 2010 8:50 AM