.NET Security Workshop
Home About Workshops Articles Writing Talks Books Contact

16. Encrypted XML

Microsoft have embraced XML and sometimes it seems that if there's a tiny piece of data floating around on a Windows machine Microsoft will want to turn it into XML. I'll leave it up to the reader to decide whether Microsoft's attitude to XML is proportionate or not.

What is clear is that business depends on XML. XML can be self describing and this means that data generated by one system can be read by any other system. This is extremely powerful because it means that data can be shared without an agreed contract. Of course, XML can be used between machines that know about each other and have an agreed data format, but this is orthogonal to the real reason for XML: it is a self describing data format.

One reason why you might want to use XML is to be able to pass it over the internet and pass it to a server through port 80. Most administrators lockdown their internet gateway, but leave port 80 (HTTP) open for text data. In this case you may want to protect data by encrypting it, but even if the data is not sensitive you will definitely want to protect it from tampering by signing it.

The W3 consortium has standards for signing and encrypting XML and the .NET framework has classes that support these standards.

16.1 Signing XML

Signing data is quite simple: you create a hash, you encrypt the hash with a certificate private key and you make the certificate public key available. The problem with signing XML (as opposed to data embedded in XML) is that XML is not exact. Two fragments of XML can obey the same schema and have the same data, but can have different whitespace and line endings. Whitespace and line endings  are unimportant in most XML, but to cryptography whitespace and line endings are bytes of data and so they are vitally important. This means that any mechanism to sign XML must take this into account by making sure that the XML is in a standard format before it is signed (or indeed, before signed XML is verified). This is done through canonicalization. In addition, XML data can be affected by stylesheets, DTDs, namespace declarations and namespace attributes.

The main class for signing data is SignedXml in the System.Security.Cryptography.Xml namespace. This class will sign an arbitrary amount of XML and it can then format the signature as XML.

Create a file (sign.cs) to test this out. Add the following code:

using System;
using System.IO;
using System.Xml;
using System.Security.Cryptography;
using System.Security.Cryptography.Xml;
using System.Security.Cryptography.X509Certificates;

class App
{
   // command line to sign: s infile outfile certfile
   // verify: v infile [certfile]
   static void Main(string[] args)
   {
      if (args.Length < 2)
      {
         Console.WriteLine("Usage: to sign: sign s infile outfile certfile");
         Console.WriteLine(" to verify: v infile [certfile]");
         return;
      }

      bool bSign = (args[0][0] == 's');
      string inFile = args[1];
      if (!File.Exists(inFile))
      {
         Console.WriteLine("{0} does not exist", inFile);
         return;
      }

      string outFile = null;
      string certName = null;

      if (bSign)
      {
         if (args.Length < 4) return;
         outFile = args[2];
         if (File.Exists(outFile)) File.Delete(outFile);
         certName = args[3];
      }
      else
      {
         if (args.Length == 3) certName = args[2];
      }

      if (bSign)
      {
         SignDocument(inFile, outFile, certName);
      }
      else
      {
         if (VerifyDocument(inFile, certName))
         {
            Console.WriteLine("{0} is verified", inFile);
         }
         else
         {
            Console.WriteLine("{0} is corrupted", inFile);
         }
      }
   }
   static void SignDocument(string inFile, string outFile, string certName)
   {
   }
   static bool VerifyDocument(string inFile, string certName)
   {
      return false;
   }
   static X509Certificate2 GetCertificate(StoreName storeName, string certname)
   {
      X509Store store = new X509Store(storeName, StoreLocation.CurrentUser);
      store.Open(OpenFlags.ReadOnly);

      X509Certificate2Collection certs =
      store.Certificates.Find(X509FindType.FindBySubjectName, certname, false);
      store.Close();
      if (certs.Count == 0)
      {
         return null;
      }

      return certs[0];
   }
}

This code can be used to sign an XML file, or verify it. It is used in two ways. The first parameter is s to sign the document and v to verify it; the second parameter is the document to sign or verify. If the document is being signed then there must be two other parameters: the third parameter is the name of the document with the signature, the final parameter is the subject name of a certificate which has the private key to use to sign the document. If the process is being used to verify the document then there is one extra parameter, the subject name of the certificate.

You can compile this, but it will not do anything useful.

To sign the document you use the SignDocument method. Add the following code:

static void SignDocument(string inFile, string outFile, string certName)
{
   X509Certificate2 cert = GetCertificate(StoreName.My, certName);
   XmlDocument doc = new XmlDocument();
   doc.Load(inFile);
   SignedXml sig = new SignedXml(doc);
   sig.SigningKey = cert.PrivateKey;
   Reference reference = new Reference("#item");
   reference.AddTransform(new XmlDsigEnvelopedSignatureTransform());
   sig.AddReference(reference);
   sig.ComputeSignature();
   doc.DocumentElement.AppendChild(sig.GetXml());
   doc.Save(outFile);
}

This code obtains a certificate from the My store. The certificate will be used to sign the data so the private key will be used, hence it has to be in your personal store. Next, an XmlDocument is created and then initialized with the document that will be signed. Then a SignedXml is created based on this document and its SigningKey property is assigned to the private key of the certificate. You have to tell the signing object what data you want to sign and this is done through a Reference object. The constructor takes a URI, but you can chose to use the default constructor and use the Uri property instead. This URI can be one of three values:

  • Empty string: the entire document is signed
  • A URI to an external file: the 'detached data' is signed
  • An id: a specific element in the file will be signed

In this example the XML will have an item with an id attribute with a value of item. Here is the XML file (data.xml) that will be used:

<Data>
   <Order>
      <Item id="item" catalogID="42" quantity="1" />
      <CreditCard number="0000 0000 0000 0000" />
   </Order>
</Data>

For the time being the <CreditCard> element will be ignored. The data that will be signed is the <Item> element.

Next, your code has to identify how the data will be signed. This is carried out through a transform which is a class derived from the Transform class:

Class Description
X509DecryptionTransform If a document is encrypted and signed then this specifies the order that the operations will be performed
XmlDsigBase64Signature Base64 decoding transform
XmlDsigC14NTransform C14N canonicalization transform, use this for documents that do not contain comments. This is the default.
XmlDsigC14NWithCommentsTransform C14N canonicalization transform, use this for documents that contain comments.
XmlDsigEnvelopeSignatureTransform W3C enveloped signature transform.
XmlDsigExcC14NTransform Exclusive C14N canonicalization transform
XmlDsigExcC14NWithCommentsTransform Exclusive C14N canonicalization transform for documents that contain comments.
XmlDsigXPathTransform XPath transform on the digital signature
XmlDsigXsltTransform XSLT transform on the digital signature
XmlLicenseTransform License transform on the digital signature

In most cases, to use a transform you initialize an instance of the appropriate class and pass it to the AddTransform method. Each transform has an identifying URI which is available through the same-named static fields of SignedXml. The exception to this, is the canonicalization C14N transforms. You can only add one canonicalization transform and so you identify this transform by assigning the CanonicalizatioMethod of the SignedInfo object (accessed through the SignedInfo property of the SignedXml object) with the URI of the transform to use. By default, all signing will be canonicalized with C14N (the XmlDsigC14NTransform transform), so if you wish to use this transform you need take no additional action.

In this example I have used XmlDsigEnvelopeSignatureTransform which will remove the <Signature> element from the document before calculating the digest. I have provided the data that will be signed in the XML file, if you create the XML in code then you can provide this data by creating a DataObject object, initialize its Data property with the XML fragment and initialize its Id property with the identifier that will be referenced through the Uri property of the Reference object. This data can be added to the SignedXml with its AddObject method.

To use this code you will need to create a signature certificate and place it in the My store (make sure that you use your own name for the subject).

makecert -r -pe -n "CN=Richard Grimes" -sky signature -ss My

This means that a private key is created which can be used to sign data. Run the application to sign the XML you created earlier:

sign s data.xml signed.xml "Richard Grimes"

This will sign the data and add the signature to the XML and write the whole data to signed.xml:

<Data>
  <Order>
    <Item id="item" catalogID="42" quantity="1" />
    <CreditCard number="0000 0000 0000 0000" />
  </Order>
  <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/2000/09/xmldsig#rsa-sha1" />
      <Reference URI="#item">
        <Transforms>
          <Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" />
        </Transforms>
        <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />
        <DigestValue>cjYaXqvN656K7ZlvZwtHAh9867A=</DigestValue>
      </Reference>
    </SignedInfo>
    <SignatureValue>
      oGNeLaPUR4j8bfk97AlLKGQ91827zWyJhTtYevrpNdnttDE4g4L9iKC3pg7RTD2OAkbyyfxz2c/W0Lrm
      oQAd/idqlWD5xdCaN5fmU5LbfBaO5R4IeofOV+g6lo0QP79xxom7g+N+4tcZiUwiuInf3XEDXv3xW+JO
      f2jXfp7vf9g=
    </SignatureValue>;
  </Signature>
</Data>

Here you can see that a <Signature> element has been added with the signature in the <SignatureValue> element and information about how the signature was created in the <SignedInfo> element. You can see that the signing mechanism was normalized with the C14N transform (which is the default for SignedXml). The signature is performed on the element with the id of item, there is just one <Reference> here, but other documents may have more than one <Reference> element.

During the signing process the document is normalized with the specified canonicalization method and then each reference is processed by applying each transform in its <Transforms> collection in the order given. Next, a digest is calculated for each reference and in this case an SHA1 hash is calculated on the transformed data (that is, the data without the <Signature> element, hence the enveloped-signature transform algorithm). The digest that is created is put in the <DigestValue> element. Finally, the <SignedInfo> element, and hence all of the <Reference> elements, is hashed and signed with the private key given by the SigningKey property, the signature is then put in the  <SignatureValue> element.

Now you need to verify this data, add the following code:

static bool VerifyDocument(string inFile, string certName)
{
   X509Certificate2 cert = null;
   if (certName != null)
      cert = GetCertificate(StoreName.AddressBook, certName);
   XmlDocument doc = new XmlDocument();
   doc.Load(inFile);
   SignedXml sig = new SignedXml(doc);
   XmlElement signature = doc.DocumentElement["Signature"];
   sig.LoadXml(signature);

   bool bVerified = false;
   if (cert == null)
      bVerified = sig.CheckSignature();
   else
      bVerified = sig.CheckSignature(cert, true);
   return bVerified;
}

Compile this code. First, it checks to see if a certificate subject has been given, and if so a certificate is obtained from the AddressBook store. To use this code you will have to put the certificate and hence your public key, in the AddressBook store. The steps to do this were given on page fourteen for a key exchange example, but the same steps can be performed here. As before, the XML document is loaded and a SignedXml object is created based on this document. However, since the action is to verify the signature you need to specify the item that holds the signature, so the code obtains the <Signature> element. and passes it to the LoadXml method. Finally, the signature is checked with CheckSignature passing the certificate. The second parameter indicates whether just the signature should be checked (true, as in this case) or whether the signature and the certificate should be checked (false). Since this example uses a test certificate any check on the certificate will fail, hence the fact that this example indicates that only the signature should be checked.

Assuming that you have put the certificate in the AddressBook store, you can now verify the data using:

sign v signed.xml "Richard Grimes"

This should indicate that the data in the file has been verified.

In this example you have had to use the certificate in the AddressBook to verify the signature (in effect the public key is used to decrypt the signature and this is compared with the hash that the verification method will calculate over the references). Clearly, on your machine a certificate in the My store could be used, but to make this simulation authentic an AddressBook certificate is used because in a real life situation the verification is more likely to be performed on another machine. Another possibility is to place the public key in the XML file which means that the recipient does not need to have the certificate. To do this add the following code to the SignDocument method:

static void SignDocument(string inFile, string outFile, string certName)
{
   X509Certificate2 cert = GetCertificate(StoreName.My, certName);
   XmlDocument doc = new XmlDocument();
   doc.Load(inFile);
   SignedXml sig = new SignedXml(doc);
   sig.SigningKey = cert.PrivateKey;
   Reference reference = new Reference("#item");
   reference.AddTransform(new XmlDsigEnvelopedSignatureTransform());
      sig.AddReference(reference);

   KeyInfo keyInfo = new KeyInfo();
   keyInfo.AddClause(new KeyInfoX509Data(cert));
   sig.KeyInfo = keyInfo;

   sig.ComputeSignature();
   doc.DocumentElement.AppendChild(sig.GetXml());
   doc.Save(outFile);
}

Compile this code and run the process to generate the signature. Now view the signature file (type signed.xml), and you'll see a new element at the bottom of the <Signature> element:

   <SignatureValue>
      <!-- data -->
   </SignatureValue>
   <KeyInfo>
      <X509Data>
         <X509Certificate>
             <!-- data -->
         </X509Certificate>
      </X509Data>
   </KeyInfo>
</Signature>

The <KeyInfo> element contains the certificate's public key. Now you can verify the signature without giving information about the certificate to use:

sign v signed.xml

Yet again this should verify the data.

Finally, to confirm that the verification process is performed correctly change the quantity attribute in the signed.xml document to a different value and then run the verification. You'll find that a message signed.xml is corrupted will be displayed. Return the quantity attribute back to its original value (1) and change the number attribute in the <CreditCard> element. Run the verification again. This time you'll find that the document is verified. The reason is that the code specifies that only the element with the id of item will be signed.

Clean up this example by removing the certificates that you added to the My store and the AddressBook store.

16.2 Encrypting XML

XML encryption is performed using the EncryptXml class. This class can encrypt arbitrary binary data, or it can encrypt XML; it can use a symmetric algorithm, or it can use asymmetric cryptography to encrypt a symmetric session key. Let's start with encrypting using a symmetric key. Create a file (s_encrypt.cs) and add the following:

using System;
using System.Security.Cryptography;
using System.Security.Cryptography.Xml;
using System.Xml;
using System.IO;

class App
{
   // parameters: e|d infile outfile passphrase
   static void Main(string[] args)
   {
      if (args.Length < 4) return;
      bool bEncrypt = (args[0][0] == 'e');
      string infile = args[1];
      if (!File.Exists(infile)) return;
      string outfile = args[2];
      if (File.Exists(outfile)) File.Delete(outfile);

      string passphrase = args[3];

      Rijndael key = new RijndaelManaged();
      PasswordDeriveBytes pdb = new PasswordDeriveBytes(passphrase, null);
      key.Key = pdb.GetBytes(key.KeySize / 8);

      if (bEncrypt)
      {
         Encrypt(infile, outfile, key);
      }
      else
      {
         Decrypt(infile, outfile, key);
      }
   }
   static void Encrypt(string infile, string outfile, SymmetricAlgorithm key)
   {
   }
   static void Decrypt(string infile, string outfile, SymmetricAlgorithm key)
   {
   }
}

The first parameter is e for encryption and d for decryption. The second parameter is the name of the file that has the section to be encrypted and the third parameter is the name of the file with the encrypted section. The final parameter is the pass phrase that will be used to encrypt the data. The actual key is generated from the pass phrase using the PasswordDeriveBytes class. To simplify the code in this case, I am not using salt.

Compile this to check that there are no syntax errors (csc s_encrypt.cs).

Now add the following:

static void Encrypt(string infile, string outfile, SymmetricAlgorithm key)
{
   XmlDocument doc = new XmlDocument();
   doc.Load(infile);
   XmlElement elem = (XmlElement)doc.GetElementsByTagName("CreditCard")[0];
   EncryptedXml encXml = new EncryptedXml();
   byte[] enc = encXml.EncryptData(elem, key, false);
   EncryptedData ed = new EncryptedData();
   ed.Type = EncryptedXml.XmlEncElementUrl;
   ed.EncryptionMethod = new EncryptionMethod(EncryptedXml.XmlEncAES256Url);
   ed.CipherData.CipherValue = enc;
   EncryptedXml.ReplaceElement(elem, ed, false);
   doc.Save(outfile);
}

This loads the XML file and then gets access to the element that will be encrypted. This example uses the data.xml file used in the last example, and the section that will be encrypted is the <CreditCard> element. This element is encrypted with an instance of the EncryptedXml class using the EncryptData method. The first parameter of this method is the element to encrypt, the second is the key, and the third indicates whether the entire element is encrypted (false, as in this case) or if just the contents of the element is encrypted. There are two other encryption methods on EncryptedXml: one called EncryptKey that you can use with an asymmetric key to encrypt the session key that was used to encrypt the data and the other is called Encrypt which is used to encrypt the data using a certificate (which we'll do later). If you use EncryptKey it means that you want to create a session key and pass this to the recipient using key exchange, in which case you'll have to use the EncryptedKey and KeyInfo classes to create an <EncryptedKey> element in the output XML. There is a DecryptKey method for you to decrypt this data to access the session key.

The output XML will replace the <CreditCard> element with an <EncryptedData> element and so to do this you have to create an EncryptedData object. The Type property of this object indicates the type of data that is encrypted, and in this case the constant string indicates that the data is an element. The EncryptionMethod indicates the cryptographic algorithm that was used, and again this is provided by a constant string in the EncryptedXml class. Finally, the actual encrypted data is placed in the CipherData property. The EncryptedData object can now be used to replace the element that was encrypted and the data is saved to the new file.

Compile this code and run it:

s_encrypt e data.xml enc.xml "secret code"

Now take a look at the file that is created:

<Data>
  <Order>
    <Item id="item" catalogID="42" quantity="1" />
    <EncryptedData Type="http://www.w3.org/2001/04/xmlenc#Element"
        xmlns="http://www.w3.org/2001/04/xmlenc#">
      <EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#aes256-cbc"/>
      <CipherData>
        <CipherValue>
          fZNFbL6ldReMxfOmNbmmrqVTQZF44S4uyfwT2RYpsc9OaqvokrAzu/9d3Kot84sZ1txYwUzDYG/hCVPWKuvXPg==
        </CipherValue>
      </CipherData>
    </EncryptedData>
  </Order>
</Data>

Here, you can identify the XML generated from the EncryptedData object that you created. This shows that the Type property corresponds to the Type attribute, the EncryptedMethod property corresponds to the <EncryptedMethod> element and the CipherData property corresponds to the <CipherData> element.

The code to decrypt the data is straightforward. Add the following code:

static void Decrypt(string infile, string outfile, SymmetricAlgorithm key)
{
   XmlDocument doc = new XmlDocument();
   doc.Load(infile);
   XmlElement elem = (XmlElement)doc.GetElementsByTagName("EncryptedData")[0];
   EncryptedData ed = new EncryptedData();
   ed.LoadXml(elem);
   EncryptedXml encXml = new EncryptedXml();
   byte[] dec = encXml.DecryptData(ed, key);
   encXml.ReplaceData(elem, dec);
   doc.Save(outfile);
}

This code loads the document and then gets access to the encrypted data. The element it loads is then used to initialize an EncryptedData object which is then decrypted using the DecryptData method on the EncryptedXml class.

Compile this process and run it again:

s_encrypt d enc.xml dec.xml "secret code"

Now type the output file to the console (type dec.xml) and confirm that the result is the original file that was encrypted.

Often you want to encrypt XML so that you can transmit it to another user. Using a symmetric key like this means that the other user must have access to the key, which brings up the issue of key distribution. Usually it makes more sense to use asymmetric cryptography to remove the problems of symmetric key distribution. The simplest way to do this is with a certificate and this produces similar results as the ProtectedSection method used on the last page. The EncryptXml class creates a symmetric session key to encrypt the data that you identify. Then the class encrypts the session key with the public key in the certificate. This gives two encrypted values and both are used to replace the element in the XML.

Thus the certificate is used in key exchange and so you need to create a key exchange certificate. The steps to do this are outlined on page fourteen. Encryption is carried out using the public key and so usually this will be done with a certificate in the AddressBook store. The machine that will encrypt the XML should import the certificate  into that store. To decrypt the data you will use the private key, so the certificate must be in the My store. Page fourteen shows how to perform this process (key generation for the certificate for the machine that will receive the encrypted data, export the certificate with just the public key, and then import this certificate on to the machine that will encrypt the data) on a single machine.

Once you have created the certificate create a file (a_encrypt.cs) with the following code:

using System;
using System.IO;
using System.Xml;
using System.Security.Cryptography;
using System.Security.Cryptography.Xml;
using System.Security.Cryptography.X509Certificates;

class App
{
   // command line to encrypt: e infile outfile certfile
   // decrypt: d infile outfile
   static void Main(string[] args)
   {
      if (args.Length < 2) return;
      bool bEncrypt = (args[0][0] == 'e');
      string inFile = args[1];
      if (!File.Exists(inFile)) return;
      string outFile = null;
      outFile = args[2];
      if (File.Exists(outFile)) File.Delete(outFile);
      string certName = null;

      if (bEncrypt)
      {
         if (args.Length < 4) return;
         certName = args[3];
      }

      if (bEncrypt)
         EncryptDocument(inFile, outFile, certName);
      else
         DecryptDocument(inFile, outFile);
   }

   static void EncryptDocument(string inFile, string outFile, string certName)
   {
   }

   static void DecryptDocument(string inFile, string outFile)
   {
   }

   static X509Certificate2 GetCertificate(StoreName storeName, string certname)
   {
      X509Store store = new X509Store(storeName, StoreLocation.CurrentUser);
      store.Open(OpenFlags.ReadOnly);

      X509Certificate2Collection certs =
      store.Certificates.Find(X509FindType.FindBySubjectName, certname, false);
      store.Close();
      if (certs.Count == 0) return null;
      return certs[0];
   }
}

This just checks the parameters to determine what to do. Notice that with encryption you have to provide the subject name in the certificate, and information about the certificate will be placed in the cyphertext so there is no need to provide a subject name for decryption.

Next, add a method to encrypt the data, yet again, this will encrypt the <CreditCard> element in the data.xml file.

static void EncryptDocument(string inFile, string outFile, string certName)
{
   X509Certificate2 cert = GetCertificate(StoreName.AddressBook, certName);
   XmlDocument doc = new XmlDocument();
   doc.Load(inFile);
   XmlElement elem = (XmlElement)doc.GetElementsByTagName("CreditCard")[0];
   EncryptedXml encXml = new EncryptedXml();
   EncryptedData enc = encXml.Encrypt(elem, cert);
   EncryptedXml.ReplaceElement(elem, enc, false);
   doc.Save(outFile);
}

This code is straightforward: first the certificate is obtained from the AddressBook store and then the document is loaded. Next, the appropriate element is obtained and this is encrypted using the Encrypt method. Notice that this method creates an EncryptedData object so you do not have to take any steps other than to use this element to replace the original.

Compile this code (csc a_encrypt.cs) and run it with these parameters:

a_encrypt e data.xml enc.xml "Richard Grimes"

You will get results like this:

<Data>
  <Order>
    <Item id="item" catalogID="42" quantity="1" />
    <EncryptedData Type="http://www.w3.org/2001/04/xmlenc#Element" xmlns="http://www.w3.org/2001/04/xmlenc#">
      <EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#aes256-cbc" />
      <KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
        <EncryptedKey xmlns="http://www.w3.org/2001/04/xmlenc#">
          <EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#rsa-1_5" />
          <KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
            <X509Data>
              <X509Certificate><data><X509Certificate>
            </X509Data>
          </KeyInfo>
          <CipherData>
            <CipherValue><data></CipherValue>
          </CipherData>
        </EncryptedKey>
      </KeyInfo>
      <CipherData>
        <CipherValue><data></CipherValue>
      </CipherData>
    </EncryptedData>
  </Order>
</Data>

Notice that there are two <CipherData> elements. The first is part of the <EncryptedKey> element and is the session key encrypted using the public key of the certificate identified by the <KeyInfo> element. The second <CipherData> element is the actual data encrypted with the session key. To decrypt the data add the following code:

static void DecryptDocument(string inFile, string outFile)
{
   XmlDocument doc = new XmlDocument();
   doc.Load(inFile);
   EncryptedXml encXml = new EncryptedXml(doc);
   encXml.DecryptDocument();
   doc.Save(outFile);
}

This code is even simpler. The data is decrypted using DecryptDocument which will decrypt all <EncryptedData> elements in the document. This method will read the certificate information in the <KeyInfo> element and will use this to locate the appropriate certificate in the My store.

Compile this code and run it:

a_encrypt d enc.xml dec.xml

Compare the output data (dec.xml) with the original file and confirm that they are the same.

Finally, clean up this example by removing the certificate that you added to the My store and the AddressBook store.

I hope that you enjoy this tutorial and value the knowledge that you will gain from it. I am always pleased to hear from people who use this tutorial (contact me). If you find this tutorial useful then please also email your comments to mvpga@microsoft.com.

Errata

If you see an error on this page, please contact me and I will fix the problem.

Page Seventeen

This page is (c) 2007 Richard Grimes, all rights reserved