Microsoft KB Archive/320602

From BetaArchive Wiki

Article ID: 320602

Article Last Modified on 1/26/2004



APPLIES TO

  • Microsoft .NET Framework Class Libraries 1.1
  • Microsoft Visual C# .NET 2003 Standard Edition
  • Microsoft Visual C# .NET 2002 Standard Edition



This article was previously published under Q320602

SUMMARY

This step-by-step article describes how to use certificates to sign and to verify SignedXml objects. The article describes how to use the platform invoke services in Visual Basic .NET to call Win32 API functions to access certificates in certificate stores and to attach their keys to SignedXml objects to sign and to verify the objects. The article also describes how to use platform invoke services to call Win32 API functions to extract the public key from an X509Certificate object.

This article refers to Win32 API functions as "functions".

back to the top

Requirements

This article assumes that you are familiar with programming with Microsoft Visual C# .NET.

The following list outlines the recommended hardware, software, network infrastructure, and service packs that you need:

  • Microsoft Windows XP or Microsoft Windows 2000
  • Microsoft Visual Studio .NET

back to the top

MORE INFORMATION

Enumerate the certificates

The following points describe the key parts of the sample code that follows.

  • Call the CertOpenSystemStore function to obtain a handle to the user's personal certificate store.

    uint hCertStore = Crypt32.CertOpenSystemStore(0, "My");
  • Call the CertEnumCertificatesInStore function to obtain certificate context pointers to the certificates.

    while (0 != (pCertContext = Crypt32.CertEnumCertificatesInStore(hCertStore, (uint)pCertContext)))
  • Create an X509Certificate object from a certificate context pointer.

    X509Certificate x509 = new X509Certificate((IntPtr)pCertContext);
  • Call the CertDuplicateCertificateContext function to increment the reference count on the certificate context pointer so that you can use this value later.

    CertDuplicateCertificateContext((IntPtr)pCertContext);

You can use the following code to enumerate the certificates.

To summarize the code, you enumerate the certificates in the "My" certificate store, populate a ListView control with the subject and issuer names of each certificate, and then attach a certificate context pointer to the Tag property of each ListView item.

private void button1_Click(object sender, System.EventArgs e)   
{
listView1.Items.Clear();
uint hCertStore = Crypt32.CertOpenSystemStore(0, "My");
if (hCertStore == 0)
 {
 MessageBox.Show("CertOpenSystemStore failed: " + Marshal.GetLastWin32Error().ToString());
 return;
 }
uint pCertContext = Crypt32.CertEnumCertificatesInStore(hCertStore, (uint)0);
while (pCertContext != 0) 
 {
 X509Certificate x509 = new X509Certificate((IntPtr)pCertContext);
 ListViewItem NewItem = listView1.Items.Add(x509.GetName());
 NewItem.SubItems.Add(x509.GetIssuerName());

// Increment the reference count so that you can use this value later.

 NewItem.Tag = Crypt32.CertDuplicateCertificateContext(pCertContext);
 pCertContext = Crypt32.CertEnumCertificatesInStore(hCertStore, pCertContext);
 } 
}

back to the top

Sign a SignedXml object by using a certificate

  1. To do the following tasks, call the CertGetCertificateContextProperty function:

    • To query for the cryptographic service provider name.
    • To query for the cryptographic service provider type.
    • To query for the key container name that is associated with a particular certificate context.
    • To pack this information into an RSACryptoServiceProvider object.
    public RSACryptoServiceProvider RSACryptoServiceProviderFromCertContext (IntPtr pCertContext)
    {
      // Determine the size of the buffer that you need to allocate.
    
      uint cbData = 0;
      bool fStatus = Crypt32.CertGetCertificateContextProperty(pCertContext.ToInt32(),
      CERT_KEY_PROV_INFO_PROP_ID, (IntPtr)0, ref cbData);
      if (!fStatus)
     {
      MessageBox.Show("CertGetCertificateContextProperty failed: " + Marshal.GetLastWin32Error().ToString());
    
      // Get the CERT_KEY_PROV_HANDLE_PROP_ID value and the HCRYPTPROV value.
    
     cbData = 4;
     IntPtr pCryptKeyProvInfo = Marshal.AllocHGlobal(new IntPtr(cbData));
     fStatus = Crypt32.CertGetCertificateContextProperty(pCertContext.ToInt32(), CERT_KEY_PROV_INFO_PROP_ID,
     pCryptKeyProvInfo, 
     ref cbData);
    
     if (!fStatus)
      {
       MessageBox.Show("CertGetCertificateContextProperty failed: " + Marshal.GetLastWin32Error().ToString());
      }
    return null;
    }
    
    if (cbData != 0)
    {
    // Allocate an unmanaged buffer to store the CRYPT_KEY_PROV_INFO structure.
    
    IntPtr pCryptKeyProvInfo = Marshal.AllocHGlobal((int)cbData);
    
    // Get the CRYPT_KEY_PROV_INFO structure.
    
    fStatus = Crypt32.CertGetCertificateContextProperty(
    pCertContext.ToInt32(), 
    CERT_KEY_PROV_INFO_PROP_ID, 
    pCryptKeyProvInfo, 
    ref cbData);
    
    if (!fStatus)
    {
     MessageBox.Show("CertGetCertificateContextProperty failed: " + Marshal.GetLastWin32Error().ToString());
     Marshal.FreeHGlobal(pCryptKeyProvInfo);
    }
    else
    { 
    // Build a CspParameters object with the provider type, the provider name,
    // and the container name from the CRYPT_KEY_PROV_INFO structure.
    // The pointer to the container name is the first DWORD in the CRYPT_KEY_PROV_INFO
    // structure, the pointer to the provider name is the second DWORD, and 
    // the provider type is the third DWORD.
    
    try
     {
     CspParameters CspParams = new CspParameters(Marshal.ReadInt32((IntPtr)((int)pCryptKeyProvInfo + 8)), 
     Marshal.PtrToStringUni((IntPtr)Marshal.ReadInt32((IntPtr)((int)pCryptKeyProvInfo + 4))), 
     Marshal.PtrToStringUni((IntPtr)Marshal.ReadInt32(pCryptKeyProvInfo)));
     // Free the unmanaged CRYPT_KEY_PROV_INFO buffer.
     Marshal.FreeHGlobal(pCryptKeyProvInfo);
     return new RSACryptoServiceProvider(CspParams);
     }
    catch(Exception ex)
     {
      MessageBox.Show(ex.Message);
     }
    }
    }
    return null;
    }
  2. Import the provider name, the provider type, and the key container information in the RSACryptoServiceProvider object to a System.Security.Cryptography.RSA object.

    RSA key = RSA.Create();
    key.ImportParameters(rsacsp.ExportParameters(true));
  3. Attach the System.Security.Cryptography.RSA object to the SignedXml object, and then compute the signature.

    signedXml.SigningKey = key;
    signedXml.ComputeSignature();

back to the top

Verify a SignedXml object by using a certificate

  1. Extract the exponent and the modulus from an X509Certificate object, and then pack the exponent and the modulus into an RSACryptoServiceProvider object.

    /// <summary>
    /// This function creates and returns an RSACryptoServiceProvider object
    /// that contains only the public key, based on the supplied X509Certificate object.
    /// </summary>
    /// <param name="x509"></param>
    /// <returns></returns>
    
    public RSACryptoServiceProvider GetPublicKeyFromX509Certificate (X509Certificate x509)
     {
     RSACryptoServiceProvider rsacsp = null;
     uint hProv = 0;
     IntPtr pPublicKeyBlob = IntPtr.Zero;
    // Get a pointer to a CERT_CONTEXT structure from the raw certificate data.
     IntPtr pCertContext = IntPtr.Zero;
     pCertContext = (IntPtr)Crypt32.CertCreateCertificateContext(
     X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
     x509.GetRawCertData(),
     x509.GetRawCertData().Length);
     if (pCertContext == IntPtr.Zero)
     {
      MessageBox.Show("CertCreateCertificateContext failed: " + Marshal.GetLastWin32Error().ToString());
      goto Cleanup;
     }
     if (!Crypt32.CryptAcquireContext(ref hProv, null, null, PROV_RSA_FULL, 0)) 
      { 
       if (!Crypt32.CryptAcquireContext(ref hProv, null, null, PROV_RSA_FULL, CRYPT_NEWKEYSET)) 
       { 
        MessageBox.Show("CryptAcquireContext failed: " + Marshal.GetLastWin32Error().ToString());
       goto Cleanup;
      }
     }
    
    // Get a pointer to the CERT_INFO structure.
    // This pointer is the fourth DWORD of the CERT_CONTEXT structure.
    
    IntPtr pCertInfo = (IntPtr)Marshal.ReadInt32(pCertContext, 12);
    
    // Get a pointer to the CERT_PUBLIC_KEY_INFO structure.
    // This structure is located starting at the fifty-seventh byte
    // of the CERT_INFO structure.
    
    IntPtr pSubjectPublicKeyInfo = (IntPtr)(pCertInfo.ToInt32() + 56);
    
    // Import the public key information from the certificate context
    // into a key container by passing the pointer to the SubjectPublicKeyInfo
    // member of the CERT_INFO structure to the CryptImportPublicKeyInfoEx
    // Win32 API function.
    
    uint hKey = 0;
    if (!Crypt32.CryptImportPublicKeyInfo(hProv, X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
    pSubjectPublicKeyInfo, ref hKey)) 
     {
      MessageBox.Show("CryptImportPublicKeyInfoEx failed: " + Marshal.GetLastWin32Error().ToString());
      goto Cleanup;
     }
    
    // Get the size of the buffer that is needed to contain the PUBLICKEYBLOB structure, and then
    // call the CryptExportKey Win32 API function to export the public key to the PUBLICKEYBLOB format.
    
     uint dwDataLen = 0;
     if (!Crypt32.CryptExportKey(hKey, 0, PUBLICKEYBLOB, 0, 0, ref dwDataLen))
     {
      MessageBox.Show("CryptExportKey failed: " + Marshal.GetLastWin32Error().ToString());
      goto Cleanup;
     }
    
    // Export the public key to the PUBLICKEYBLOB format.
    
    pPublicKeyBlob = Marshal.AllocHGlobal((int)dwDataLen);
    if (!Crypt32.CryptExportKey(hKey, 0, PUBLICKEYBLOB, 0, (uint)pPublicKeyBlob.ToInt32(), ref dwDataLen))
     {
      MessageBox.Show("CryptExportKey failed: " + Marshal.GetLastWin32Error().ToString());
      goto Cleanup;
     }
    
    // Get the public exponent.
    // The public exponent is located in bytes 17 through 20 of the 
    // earlier PUBLICKEYBLOB structure.
    
    byte[] Exponent = new byte[4];
    Marshal.Copy((IntPtr)(pPublicKeyBlob.ToInt32() + 16), Exponent, 0, 4);
    Array.Reverse(Exponent); 
    
    // Reverse the byte order.
    // Get the length of the modulus.
    // To do this, extract the bit length of the modulus from the PUBLICKEYBLOB structure.
    // The bit length of the modulus is located in bytes 13 through 17 of the PUBLICKEYBLOB structure.
    
    int BitLength = Marshal.ReadInt32(pPublicKeyBlob, 12);
    
    // Get the modulus.
    // The modulus starts at the twenty-first byte of the PUBLICKEYBLOB structure,
    // and is BitLength/8 bytes in length.
    
    byte[] Modulus = new byte[BitLength / 8];
    Marshal.Copy((IntPtr)(pPublicKeyBlob.ToInt32() + 20), Modulus, 0, BitLength / 8);
    Array.Reverse(Modulus); 
    
    // Reverse the byte order.
    // Put the modulus and the exponent into an RSAParameters object.
    
    RSAParameters rsaparms = new RSAParameters();
    rsaparms.Exponent = Exponent;
    rsaparms.Modulus = Modulus;
    
    // Import the modulus and the exponent into an RSACryptoServiceProvider object
    // by using the RSAParameters object.
    
    rsacsp = new RSACryptoServiceProvider(); 
    
    rsacsp.ImportParameters(rsaparms);
    Cleanup:
    
    if (pCertContext != IntPtr.Zero)
    Crypt32.CertFreeCertificateContext(pCertContext.ToInt32());
    
    if (hProv != 0)
    Crypt32.CryptReleaseContext(hProv, 0);
    
    if (pPublicKeyBlob != IntPtr.Zero)
    Marshal.FreeHGlobal(pPublicKeyBlob);
    return rsacsp;
    }
    uint hCrypt;
    RSACryptoServiceProvider rsacsp;
    private void button2_Click(object sender, System.EventArgs e)
    {
     if(listView1.SelectedItems.Count == 0)
      {
       MessageBox.Show("You must select at least one certificate in the list view");
       return;
      }
    hCrypt  = (uint)listView1.SelectedItems[0].Tag;
    rsacsp = RSACryptoServiceProviderFromCertContext(new IntPtr((int)hCrypt));
    if(rsacsp == null)
    return;
    
    // Create the SignedXml message.
    
    SignedXml signedXml = new SignedXml();
    RSA key = RSA.Create();
    key.ImportParameters(rsacsp.ExportParameters(true));
    signedXml.SigningKey = key;
    
    // Create a data object to store the data to sign.
    
    System.Security.Cryptography.Xml.DataObject dataObject = new System.Security.Cryptography.Xml.DataObject();
    dataObject.Data = xmlDocument.ChildNodes;
    dataObject.Id = "MyObjectId";
    
    // Add the data object to the signature.
    
    signedXml.AddObject(dataObject);
    
    // Create a reference to package information into the message.
    
    Reference reference = new Reference();
    reference.Uri = "#MyObjectId";
    
    // Add the reference to the message.
    
    signedXml.AddReference(reference);
    
    // Compute the signature.
    
    signedXml.ComputeSignature();
    
    // Get the XML representation of the signature.
    
    XmlElement xmlSignature = signedXml.GetXml();
    textBox2.Text = xmlSignature.OuterXml;
    }
  2. To verify the signature, pass the RSACryptoServiceProvider object that contains the X509Certificate public key to the CheckSignature method of the SignedXml object.

    if (signedXml.CheckSignature(rsa))
     MessageBox.Show ("The signature has been validated.");
    else
     MessageBox.Show ("The signature has NOT been validated.");

back to the top

Step-by-step sample code

  1. Start Visual Studio .NET.
  2. On the File menu, point to New, and then click Project.
  3. Under Project Types, click Visual C# Projects.
  4. Under Templates, click Windows Application, and then click OK.
  5. On the Project menu, click Add Reference.
  6. On the .NET tab of the Add Reference dialog box, click System.Security, and then click Select.
  7. Click OK to close the Add Reference dialog box.
  8. Replace the existing code in the Form1.cs file with the sample code that is included at the end of this section.
  9. On the Debug menu, click Start. By default, the Form1 form appears.
  10. On Form1, click EnumCert.

    All the certificates in your computer are listed in the box that is located above the EnumCert button.
  11. In the box that is located above the EnumCert button, follow these steps:
    1. To sign a SignedXml object by using the selected certificate, select a code signature certificate, and then click Sign.
    2. To verify a SignedXml object by using the selected certificate, select a code signature certificate, and then click Verify.

Code for step 8

using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Xml;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Security.Cryptography.Xml;
namespace _320602
{
   /// <summary>
   /// Summary description for Form1.
   /// </summary>
   public class Form1 : System.Windows.Forms.Form
   {
      private System.Windows.Forms.ListView listView1;
      private System.Windows.Forms.Button button1;
      private System.Windows.Forms.TextBox tbStoreName;
      private System.Windows.Forms.ColumnHeader columnHeader1;
      private System.Windows.Forms.ColumnHeader columnHeader2;
      private System.Windows.Forms.TextBox textBox1;
      private XmlDocument xmlDocument;
      private System.Windows.Forms.Button button2;
      private System.Windows.Forms.Button button3;
      private System.Windows.Forms.TextBox textBox2;
      private System.Windows.Forms.Label label1;
      private System.Windows.Forms.Label label2;
      /// <summary>
      /// Required designer variable.
      /// </summary>
      private System.ComponentModel.Container components = null;
      private const int CERT_KEY_PROV_INFO_PROP_ID = 2;
      private const int X509_ASN_ENCODING = 1;
      private const int PROV_RSA_FULL = 1;
      private const uint PKCS_7_ASN_ENCODING = 0x00010000;
      private const uint CRYPT_VERIFYCONTEXT = 0xF0000000;
      private const int CRYPT_NEWKEYSET = 8;
      private System.Windows.Forms.Label label3;
      private System.Windows.Forms.TextBox tbFileName;
      private const int PUBLICKEYBLOB = 6; 
      public Form1()
      {
         // Required for Windows Form Designer support.
         InitializeComponent();
         xmlDocument = new XmlDocument();
         XmlNode  node = xmlDocument.CreateNode(XmlNodeType.Element, "", "MyElement", "samples");
         node.InnerText = "This is a sample code to demonstrate SignedXml";
         xmlDocument.AppendChild(node);
         textBox1.Text = xmlDocument.OuterXml;
      }
      /// <summary>
      /// Clean up any resources that are being used.
      /// </summary>
      protected override void Dispose( bool disposing )
      {
         if( disposing )
         {
            if (components != null) 
            {
               components.Dispose();
            }
         }
         base.Dispose( disposing );
      }
        #region Windows Form Designer generated code
      /// <summary>
      /// Required method for Designer support. Do not modify
      /// the contents of this method by using the code editor.
      /// </summary>
      private void InitializeComponent()
      {
         this.listView1 = new System.Windows.Forms.ListView();
         this.columnHeader1 = new System.Windows.Forms.ColumnHeader();
         this.columnHeader2 = new System.Windows.Forms.ColumnHeader();
         this.button1 = new System.Windows.Forms.Button();
         this.tbStoreName = new System.Windows.Forms.TextBox();
         this.textBox1 = new System.Windows.Forms.TextBox();
         this.button2 = new System.Windows.Forms.Button();
         this.button3 = new System.Windows.Forms.Button();
         this.textBox2 = new System.Windows.Forms.TextBox();
         this.label1 = new System.Windows.Forms.Label();
         this.label2 = new System.Windows.Forms.Label();
         this.label3 = new System.Windows.Forms.Label();
         this.tbFileName = new System.Windows.Forms.TextBox();
         this.SuspendLayout();
         // 
         // listView1
         // 
         this.listView1.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] 
         {
            this.columnHeader1,
            this.columnHeader2
         });
         this.listView1.Location = new System.Drawing.Point(16, 16);
         this.listView1.Name = "listView1";
         this.listView1.Size = new System.Drawing.Size(392, 128);
         this.listView1.TabIndex = 0;
         this.listView1.View = System.Windows.Forms.View.Details;
         // 
         // columnHeader1
         // 
         this.columnHeader1.Text = "Certificate Name";
         this.columnHeader1.Width = 232;
         // 
         // columnHeader2
         // 
         this.columnHeader2.Text = "Issuer name";
         this.columnHeader2.Width = 149;
         // 
         // button1
         // 
         this.button1.Location = new System.Drawing.Point(16, 160);
         this.button1.Name = "button1";
         this.button1.Size = new System.Drawing.Size(80, 24);
         this.button1.TabIndex = 1;
         this.button1.Text = "EnumCert";
         this.button1.Click += new System.EventHandler(this.button1_Click);
         // 
         // tbStoreName
         // 
         this.tbStoreName.Location = new System.Drawing.Point(120, 160);
         this.tbStoreName.Name = "tbStoreName";
         this.tbStoreName.Size = new System.Drawing.Size(72, 20);
         this.tbStoreName.TabIndex = 2;
         this.tbStoreName.Text = "My";
         // 
         // textBox1
         // 
         this.textBox1.Location = new System.Drawing.Point(16, 240);
         this.textBox1.Multiline = true;
         this.textBox1.Name = "textBox1";
         this.textBox1.Size = new System.Drawing.Size(192, 168);
         this.textBox1.TabIndex = 3;
         this.textBox1.Text = "";
         // 
         // button2
         // 
         this.button2.Enabled = false;
         this.button2.Location = new System.Drawing.Point(216, 160);
         this.button2.Name = "button2";
         this.button2.Size = new System.Drawing.Size(80, 24);
         this.button2.TabIndex = 1;
         this.button2.Text = "Sign";
         this.button2.Click += new System.EventHandler(this.button2_Click);
         // 
         // button3
         // 
         this.button3.Enabled = false;
         this.button3.Location = new System.Drawing.Point(320, 160);
         this.button3.Name = "button3";
         this.button3.Size = new System.Drawing.Size(80, 24);
         this.button3.TabIndex = 1;
         this.button3.Text = "Verify";
         this.button3.Click += new System.EventHandler(this.button3_Click);
         // 
         // textBox2
         // 
         this.textBox2.Location = new System.Drawing.Point(216, 240);
         this.textBox2.Multiline = true;
         this.textBox2.Name = "textBox2";
         this.textBox2.Size = new System.Drawing.Size(192, 168);
         this.textBox2.TabIndex = 3;
         this.textBox2.Text = "";
         // 
         // label1
         // 
         this.label1.Location = new System.Drawing.Point(16, 216);
         this.label1.Name = "label1";
         this.label1.Size = new System.Drawing.Size(136, 16);
         this.label1.TabIndex = 4;
         this.label1.Text = "Raw XML";
         // 
         // label2
         // 
         this.label2.Location = new System.Drawing.Point(216, 216);
         this.label2.Name = "label2";
         this.label2.Size = new System.Drawing.Size(136, 16);
         this.label2.TabIndex = 4;
         this.label2.Text = "Signed XML";
         // 
         // label3
         // 
         this.label3.Location = new System.Drawing.Point(16, 192);
         this.label3.Name = "label3";
         this.label3.Size = new System.Drawing.Size(83, 16);
         this.label3.TabIndex = 4;
         this.label3.Text = "X509 Cert Path";
         // 
         // tbFileName
         // 
         this.tbFileName.Location = new System.Drawing.Point(120, 192);
         this.tbFileName.Name = "tbFileName";
         this.tbFileName.Size = new System.Drawing.Size(280, 20);
         this.tbFileName.TabIndex = 2;
         this.tbFileName.Text = "";
         // 
         // Form1
         // 
         this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
         this.ClientSize = new System.Drawing.Size(424, 422);
         this.Controls.Add(this.label1);
         this.Controls.Add(this.textBox1);
         this.Controls.Add(this.tbStoreName);
         this.Controls.Add(this.textBox2);
         this.Controls.Add(this.button1);
         this.Controls.Add(this.listView1);
         this.Controls.Add(this.button2);
         this.Controls.Add(this.button3);
         this.Controls.Add(this.label2);
         this.Controls.Add(this.label3);
         this.Controls.Add(this.tbFileName);
         this.Name = "Form1";
         this.Text = "Form1";
         this.ResumeLayout(false);
      }
        #endregion
      /// <summary>
      /// The main entry point for the application.
      /// </summary>
      [STAThread]
      static void Main() 
      {
         Application.Run(new Form1());
      }
      private void button1_Click(object sender, System.EventArgs e)
      {
         listView1.Items.Clear();
         uint hCertStore = Crypt32.CertOpenSystemStore(0, tbStoreName.Text);
         if (hCertStore == 0)
         {
            MessageBox.Show("CertOpenSystemStore failed: " + Marshal.GetLastWin32Error().ToString());
            return;
         }
         uint pCertContext = Crypt32.CertEnumCertificatesInStore(hCertStore, (uint)0);
         while (pCertContext != 0) 
         {
            X509Certificate x509 = new X509Certificate((IntPtr)pCertContext);
            ListViewItem NewItem = listView1.Items.Add(x509.GetName());
            NewItem.SubItems.Add(x509.GetIssuerName());
            // Increment the reference count so that you can use this value later.
            NewItem.Tag = Crypt32.CertDuplicateCertificateContext(pCertContext);
            pCertContext = Crypt32.CertEnumCertificatesInStore(hCertStore, pCertContext);
         } 
         button2.Enabled = true;
      }
      /// This function creates and returns an RSACryptoServiceProvider
      /// object that is based on the supplied certificate context pointer.
      public RSACryptoServiceProvider RSACryptoServiceProviderFromCertContext (IntPtr pCertContext)
      {
         // Determine the size of the buffer that you need to allocate.
         uint cbData = 0;
         bool fStatus = Crypt32.CertGetCertificateContextProperty(
            pCertContext.ToInt32(), 
            CERT_KEY_PROV_INFO_PROP_ID, 
            (IntPtr)0, 
            ref cbData);
         if (!fStatus)
         {
            MessageBox.Show("CertGetCertificateContextProperty failed: " + Marshal.GetLastWin32Error().ToString());
            // Get the CERT_KEY_PROV_HANDLE_PROP_ID value and the HCRYPTPROV value.
            cbData = 4;
            IntPtr pCryptKeyProvInfo = Marshal.AllocHGlobal(new IntPtr(cbData));
            fStatus = Crypt32.CertGetCertificateContextProperty(pCertContext.ToInt32(),CERT_KEY_PROV_INFO_PROP_ID, 
               pCryptKeyProvInfo, 
               ref cbData);
            if (!fStatus)
            {
               MessageBox.Show("CertGetCertificateContextProperty failed: " + Marshal.GetLastWin32Error().ToString());
            }
            return null;
         }
         if (cbData != 0)
         {
            // Allocate an unmanaged buffer to store the CRYPT_KEY_PROV_INFO structure.
            IntPtr pCryptKeyProvInfo = Marshal.AllocHGlobal((int)cbData);
            // Get the CRYPT_KEY_PROV_INFO structure.
            fStatus = Crypt32.CertGetCertificateContextProperty(pCertContext.ToInt32(),CERT_KEY_PROV_INFO_PROP_ID, 
               pCryptKeyProvInfo, 
               ref cbData);
            if (!fStatus)
            {
               MessageBox.Show("CertGetCertificateContextProperty failed: " + Marshal.GetLastWin32Error().ToString());
               Marshal.FreeHGlobal(pCryptKeyProvInfo);
            }
            else
            { 
               // Build a CspParameters object with the provider type, the provider name,
               // and the container name from the CRYPT_KEY_PROV_INFO structure.
               // The pointer to the container name is the first DWORD in the CRYPT_KEY_PROV_INFO
               // structure. The pointer to the provider name is the second DWORD. 
               // The provider type is the third DWORD.
               try
               {
                  CspParameters CspParams = new CspParameters(Marshal.ReadInt32((IntPtr)((int)pCryptKeyProvInfo + 8)), 
                     Marshal.PtrToStringUni((IntPtr)Marshal.ReadInt32((IntPtr)((int)pCryptKeyProvInfo + 4))), 
                     Marshal.PtrToStringUni((IntPtr)Marshal.ReadInt32(pCryptKeyProvInfo)));
                  // 
                  // Free the unmanaged CRYPT_KEY_PROV_INFO buffer.
                  // 
                  Marshal.FreeHGlobal(pCryptKeyProvInfo);
                  return new RSACryptoServiceProvider(CspParams);
               }
               catch(Exception ex)
               {
                  MessageBox.Show(ex.Message);
               }
            }
         }
         return null;
      }
      /// <summary>
      /// This function creates and returns an RSACryptoServiceProvider object
      /// that contains only the public key based on the supplied X509Certificate object.
      /// </summary>
      /// <param name="x509"></param>
      /// <returns></returns>
      public RSACryptoServiceProvider GetPublicKeyFromX509Certificate (X509Certificate x509)
      {
         RSACryptoServiceProvider rsacsp = null;
         uint hProv = 0;
         IntPtr pPublicKeyBlob = IntPtr.Zero;
         // Get a pointer to a CERT_CONTEXT structure from the raw certificate data.
         IntPtr pCertContext = IntPtr.Zero;
         pCertContext = (IntPtr)Crypt32.CertCreateCertificateContext(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
            x509.GetRawCertData(),x509.GetRawCertData().Length);
         if (pCertContext == IntPtr.Zero)
         {
            MessageBox.Show("CertCreateCertificateContext failed: " + Marshal.GetLastWin32Error().ToString());
            goto Cleanup;
         }
         if (!Crypt32.CryptAcquireContext(ref hProv, null, null, PROV_RSA_FULL, 0)) 
         { 
            if (!Crypt32.CryptAcquireContext(ref hProv, null, null, PROV_RSA_FULL, CRYPT_NEWKEYSET)) 
            { 
               MessageBox.Show("CryptAcquireContext failed: " + Marshal.GetLastWin32Error().ToString());
               goto Cleanup;
            }
         }
         
         // Get a pointer to the CERT_INFO structure.
         // This pointer is the fourth DWORD of the CERT_CONTEXT structure.
         
         IntPtr pCertInfo = (IntPtr)Marshal.ReadInt32(pCertContext, 12);
         
         // Get a pointer to the CERT_PUBLIC_KEY_INFO structure.
         // This structure is located starting at the fifty-seventh byte
         // of the CERT_INFO structure.
         
         IntPtr pSubjectPublicKeyInfo = (IntPtr)(pCertInfo.ToInt32() + 56);
         
         // Import the public key information from the certificate context
         // into a key container by passing the pointer to the SubjectPublicKeyInfo
         // member of the CERT_INFO structure to the CryptImportPublicKeyInfoEx
         // Win32 API function.
         
         uint hKey = 0;
         if (!Crypt32.CryptImportPublicKeyInfo(hProv,X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
            pSubjectPublicKeyInfo,ref hKey)) 
         {
            MessageBox.Show("CryptImportPublicKeyInfoEx failed: " + Marshal.GetLastWin32Error().ToString());
            goto Cleanup;
         }

         // Get the size of the buffer that is needed to contain the PUBLICKEYBLOB structure, and then
         // call the CryptExportKey Win32 API function to export the public key to the PUBLICKEYBLOB format.

         uint dwDataLen = 0;
         if (!Crypt32.CryptExportKey(hKey, 0, PUBLICKEYBLOB, 0, 0, ref dwDataLen))
         {
            MessageBox.Show("CryptExportKey failed: " + Marshal.GetLastWin32Error().ToString());
            goto Cleanup;
         }
         
         // Export the public key to the PUBLICKEYBLOB format.
         
         pPublicKeyBlob = Marshal.AllocHGlobal((int)dwDataLen);
         if (!Crypt32.CryptExportKey(hKey, 0, PUBLICKEYBLOB, 0, (uint)pPublicKeyBlob.ToInt32(), ref dwDataLen))
         {
            MessageBox.Show("CryptExportKey failed: " + Marshal.GetLastWin32Error().ToString());
            goto Cleanup;
         }
         
         // Get the public exponent.
         // The public exponent is located in bytes 17 through 20 of the 
         // earlier PUBLICKEYBLOB structure.
         
         byte[] Exponent = new byte[4];
         Marshal.Copy((IntPtr)(pPublicKeyBlob.ToInt32() + 16), Exponent, 0, 4);
         Array.Reverse(Exponent); // Reverse the byte order.

         // Reverse the byte order.
         // Get the length of the modulus.
         // To do this, extract the bit length of the modulus from the PUBLICKEYBLOB structure.
         // The bit length of the modulus is located in bytes 13 through 17 of the PUBLICKEYBLOB structure.
         
         int BitLength = Marshal.ReadInt32(pPublicKeyBlob, 12);

         // Get the modulus.
         // The modulus starts at the twenty-first byte of the PUBLICKEYBLOB structure,
         // and is BitLength/8 bytes in length.
         
         byte[] Modulus = new byte[BitLength / 8];
         Marshal.Copy((IntPtr)(pPublicKeyBlob.ToInt32() + 20), Modulus, 0, BitLength / 8);
         Array.Reverse(Modulus);
         
         // Reverse the byte order.
         // Put the modulus and the exponent into an RSAParameters object.
         
         RSAParameters rsaparms = new RSAParameters();
         rsaparms.Exponent = Exponent;
         rsaparms.Modulus = Modulus;
         
         // Import the modulus and the exponent into an RSACryptoServiceProvider object
         // by using the RSAParameters object.
         
         rsacsp = new RSACryptoServiceProvider();
         rsacsp.ImportParameters(rsaparms);
         Cleanup:
            if (pCertContext != IntPtr.Zero)
               Crypt32.CertFreeCertificateContext(pCertContext.ToInt32());
         if (hProv != 0)
            Crypt32.CryptReleaseContext(hProv, 0);
         if (pPublicKeyBlob != IntPtr.Zero)
            Marshal.FreeHGlobal(pPublicKeyBlob);
         return rsacsp;
      }
      uint hCrypt;
      RSACryptoServiceProvider rsacsp;
      private void button2_Click(object sender, System.EventArgs e)
      {
         if(listView1.SelectedItems.Count == 0)
         {
            MessageBox.Show("You must select at least one certificate in the list view");
            return;
         }
         hCrypt  = (uint)listView1.SelectedItems[0].Tag;
         rsacsp = RSACryptoServiceProviderFromCertContext(new IntPtr((int)hCrypt));
         if(rsacsp == null)
            return;
         
         // Create the SignedXml message.
         SignedXml signedXml = new SignedXml();
         RSA key = RSA.Create();
         key.ImportParameters(rsacsp.ExportParameters(true));
         signedXml.SigningKey = key;
         
         // Create a data object to store the data to sign.
         System.Security.Cryptography.Xml.DataObject dataObject = new System.Security.Cryptography.Xml.DataObject();
         dataObject.Data = xmlDocument.ChildNodes;
         dataObject.Id = "MyObjectId";
         
         // Add the data object to the signature.
         signedXml.AddObject(dataObject);
         
         // Create a reference to package information into the message.
         Reference reference = new Reference();
         reference.Uri = "#MyObjectId";
         
         // Add the reference to the message.
         signedXml.AddReference(reference);
         
         // Compute the signature.
         signedXml.ComputeSignature();
         
         // Get the XML representation of the signature.
         XmlElement xmlSignature = signedXml.GetXml();
         textBox2.Text = xmlSignature.OuterXml;
         button3.Enabled = true;
      }
      private void button3_Click(object sender, System.EventArgs e)
      {
         // Load the XML representation.
         XmlDocument xmlDocument = new XmlDocument();
         xmlDocument.PreserveWhitespace = true;
         xmlDocument.LoadXml(textBox2.Text);
         SignedXml signedXml = new SignedXml(xmlDocument);
         XmlNodeList nodeList = xmlDocument.GetElementsByTagName("Signature");
         signedXml.LoadXml((XmlElement)nodeList[0]);
         
         // Create a SignedXml object.
         X509Certificate x509cert = X509Certificate.CreateFromCertFile(tbFileName.Text);
         RSACryptoServiceProvider rsacsp = GetPublicKeyFromX509Certificate(x509cert);
         if (signedXml.CheckSignature(rsacsp))
            MessageBox.Show ("The signature has been validated.");
         else
            MessageBox.Show ("The signature has NOT been validated."); 
      }
   }
   /// <summary>
   /// Summary description for Crypt32.
   /// </summary>
   public class Crypt32
   {
      [DllImport("Crypt32.dll", CharSet=CharSet.Auto)]
      internal extern static uint CertOpenSystemStore(int hprov, string szSubsystemProtocol);
      [DllImport("Crypt32.dll", CharSet=CharSet.Auto)]
      internal extern static uint CertEnumCertificatesInStore(uint hCertStore, uint pPrevCertContext);
      [DllImport("Crypt32.dll", CharSet=CharSet.Auto)]
      internal extern static uint CertDuplicateCertificateContext(uint pPrevCertContext);
      [DllImport("Crypt32.dll", CharSet=CharSet.Auto,SetLastError=true)]
      [return : MarshalAs(UnmanagedType.Bool)]
      internal extern static bool CertGetCertificateContextProperty(int pCertContext,int dwPropId,
         IntPtr pvData, ref uint pcbData);
      [DllImport("Crypt32.dll", CharSet=CharSet.Auto)]
      internal extern static uint CertCreateCertificateContext(uint dwCertEncodingType,
         [MarshalAs(UnmanagedType.LPArray)]byte[] pbCertEncoded, int cbCertEncoded);
      [DllImport("Advapi32.dll", CharSet=CharSet.Auto,SetLastError=true)]
      [return : MarshalAs(UnmanagedType.Bool)]
      internal extern static bool CryptAcquireContext(ref uint phProv,string pszContainer,
         string pszProvider,uint dwProvType,uint dwFlags);
      [DllImport("Crypt32.dll", CharSet=CharSet.Auto,SetLastError=true)]
      [return : MarshalAs(UnmanagedType.Bool)]
      internal extern static bool CryptImportPublicKeyInfoEx(uint hCryptProv ,uint dwCertEncodingType, 
         IntPtr pInfo, uint aiKeyAlg, uint dwFlags ,uint pvAuxInfo, ref uint phKey); 
      [DllImport("Crypt32.dll", CharSet=CharSet.Auto,SetLastError=true)]
      [return : MarshalAs(UnmanagedType.Bool)]
      internal extern static bool CryptImportPublicKeyInfo(uint hCryptProv ,uint dwCertEncodingType, 
         IntPtr pInfo, ref uint phKey); 
      [DllImport("Advapi32.dll", CharSet=CharSet.Auto,SetLastError=true)]
      [return : MarshalAs(UnmanagedType.Bool)]
      internal extern static bool CryptExportKey(uint hKey,uint hExpKey, uint dwBlobType, 
         uint dwFlags ,uint pbData, ref uint pdwDataLen);
      [DllImport("Crypt32.dll", CharSet=CharSet.Auto,SetLastError=true)]
      [return : MarshalAs(UnmanagedType.Bool)]
      internal extern static bool CertFreeCertificateContext(int pCertContext);
      [DllImport("Advapi32.dll", CharSet=CharSet.Auto,SetLastError=true)]
      [return : MarshalAs(UnmanagedType.Bool)]
      internal extern static bool CryptReleaseContext(uint hProv, uint dwFlags);
   }
}

back to the top

Troubleshooting

Use only code signature certificates to sign a SignedXml object. If you do not use code signature certificates, exceptions may occur.

back to the top

REFERENCES

For more information, visit the following Microsoft Developer Network (MSDN) Web sites:

Managing a Certificate Store's Statehttp://msdn.microsoft.com/library/en-us/wcecryp2/html/ceconManagingaCertificateStoresState.asp

EncryptTo/DecryptTo: Encryption in .NET with CryptoAPI Certificate Stores http://msdn.microsoft.com/library/en-us/dncapi/html/encryptdecrypt2a.asp


back to the top

Keywords: kbhowtomaster kbuser kbsecurity KB320602