Adding Digital Signatures to Form Data in InfoPath 2003 Using MSXML 5.0 or .NET Framework Managed Code

This content is no longer actively maintained. It is provided as is, for anyone who may still be using these technologies, with no warranties or claims of accuracy with regard to the most recent product version or service release.

Summary: Learn how to expedite the digital signatures process for your Microsoft Office InfoPath 2003 forms by building external applications that add or verify digital signatures. Find out how to build managed code applications that employ MSXML 5.0 or Microsoft .NET Framework code to verify or add digital signatures to data in InfoPath forms. Learn to set up your InfoPath form templates so they are compatible with MSXML 5.0 and .NET Framework code. Discover how to write custom code in your form templates so that users can add digital signatures without using the Digital Signatures Wizard. (40 printed pages)

Mihaela Cris, Mark Roberts

Microsoft Corporation

September 2004

Applies to: Microsoft Office InfoPath 2003, Microsoft Office InfoPath 2003 Toolkit for Visual Studio .NET

Contents

  • Overview of Digital Signatures in InfoPath 2003

  • Interoperability Between InfoPath and MSXML 5.0 Object Model

  • Interoperability Between InfoPath and .NET Framework Digital Signatures Classes

  • Conclusion

  • Additional Resources

Overview of Digital Signatures in InfoPath 2003

The digital signatures infrastructure implemented by Microsoft Office InfoPath 2003 to sign data in forms follows the World Wide Web Consortium (W3C) XML Digital Signatures Standard specifications, and is based in part on support from Microsoft XML Core Services (MSXML) 5.0 for Microsoft Office. XML Digital Signatures are specifically designed for transactions that involve XML documents and data. A powerful feature of XML Digital Signatures is the ability to sign only specific sets of data within an XML document. For more information, see the W3C XML-Signature Syntax and Processing recommendation.

For more information about how XML Digital Signatures are implemented in MSXML 5.0, see XML Digital Signatures. For an overview of the InfoPath digital signatures feature and the object model for working with digital signatures, see Digitally Signing Data in InfoPath 2003.

Although InfoPath provides its own object model for working with digital signatures, you can also use object model members implemented in MSXML 5.0 to verify and add digital signatures to InfoPath forms from code running outside of the InfoPath application. Additionally, the digital signatures InfoPath creates are fully interoperable with the classes that Microsoft .NET Framework 1.1 and 2.0 provide for working with digital signatures and XML. The signatures InfoPath creates can be verified by applications that use .NET Framework signature verification classes. Similarly, the InfoPath digital signatures infrastructure can successfully verify digital signatures created by applications that use the .NET Framework digital signatures classes when the signatures are applied to data hosted in InfoPath forms.

Code examples provided in this article show how you can build managed code console applications that add or verify digital signatures in InfoPath forms. External applications such as this can be useful if you want to perform bulk operations to verify or sign multiple forms at once. You can use either the MSXML 5.0 object model members or the .NET Framework 1.1 or 2.0 digital signature classes in your external applications to work with digital signatures in InfoPath forms. These examples also show you how to write MSXML 5.0 and .NET Framework code that is compatible with the schemas of InfoPath forms. Additionally, you can learn how to write custom form code that automatically performs the functions users carry out when they use the Digital Signatures Wizard to sign data.

Note

The digital signatures features described in this article require InfoPath 2003 with Microsoft Office 2003 Service Pack 1 or later.

Interoperability Between InfoPath and MSXML 5.0 Object Model

The code examples in this section were created with managed code (Microsoft Visual C#) in Microsoft Visual Studio .NET 2003. To use the digital signature object model members implemented in MSXML 5.0, you must first add a reference to the MSXML 5.0 type library, which is installed with InfoPath 2003. To do that in Visual Studio, on the Build menu, click Add Reference. Click the COM tab, and in the Component Name column, double-click Microsoft XML, v5.0. When you do this, Visual Studio automatically generates a COM interop assembly named Interop.MSXML2.dll that lets you work with the MSXML 5.0 object model members from managed code. Note that even though the version of MSXML is 5.0, the qualifying namespace used within the type library is MSXML2.

Verifying InfoPath Digital Signatures with MSXML 5.0 Object Model Members

The following C# code example is part of a console application that checks the validity of the first signature in an InfoPath form.

public static bool VerifySignature(string SignedXml)
{
    bool result = false;

// Create variables for working with the form and signatures.
    MSXML2.IXMLDOMDocument3 xmldoc = new MSXML2.DOMDocument50Class();
    MSXML2.IXMLDigitalSignature digsig = 
        new MSXML2.MXDigitalSignature50Class();

    // The InfoPath form needs to preserve white space.
    xmldoc.preserveWhiteSpace = true;

    // Set the W3C XML Digital Signatures namespace as the namespace
    // used in XPath expressions that specify digital signatures.
    xmldoc.setProperty("SelectionNamespaces",
        "xmlns:sig='http://www.w3.org/2000/09/xmldsig'");
            
    // Load the signed .xml file.
    if(!xmldoc.load(SignedXml))
    {
        Console.Write("Failed loading " + SignedXml);
        return result;
    }

    // Load the digital signature node.
    digsig.signature = xmldoc.selectSingleNode("//sig:Signature");

    // Get the signature key from the certificate in the signature node. 
    MSXML2.IXMLDSigKey dsigKey = digsig.createKeyFromNode(
        xmldoc.selectSingleNode("//sig:X509Data"));

    // Attempt to verify the signature. An exception is raised if 
    // the verification fails because the signature is invalid.
    try
    {
        MSXML2.IXMLDSigKey oKey = digsig.verify(dsigKey);
        Console.Write("Signature successfully verified.");
        result = true;
    }
    catch(Exception ex)
    {
        Console.Write("Signature was NOT verified.");
        result = false;
    }
    return result;
}

You can change this code to verify multiple signatures, depending on how you have defined the signature options for the set of signable data (one signature, co-signatures, or counter-signatures).

Digitally Signing an InfoPath Form with MSXML 5.0 Object Model Members

The C code example in this section is part of a console application that digitally signs an InfoPath form. For the example to work correctly, you must create an empty signature template that adds the digital signature to the signatures container node of the signed form.

To create an empty signature template

  1. Digitally sign a form in InfoPath, and then open the signed form in a text editor.

  2. In the signed form, find the Signature element.

  3. For each occurrence of the DigestValue element within the Reference element, remove the DigestValue node values, leaving just the empty <DigestValue /> tag.

  4. Remove the value in the SignatureValue element, leaving just the empty <SignatureValue /> tag.

  5. Remove the value in the KeyInfo element, leaving just the empty <KeyInfo /> tag.

    Note

    Another way to create the Signature node template structure is to capture the Signature object that is created when InfoPath calls the Create method of the Signatures collection in a customized OnSign event handler.

Your form code should look like the following example, in which the Signature node contains the signature template that is used to create the signature.

<?xml version="1.0" encoding="UTF-8" ?> 
      <?mso-infoPathSolution solutionVersion="1.0.0.127" productVersion=
      "11.0.6353" PIVersion="1.0.0.0" name="urn:MyTrusted:MyCertificates"
       ?> 
    <?mso-application progid="InfoPath.Document"?>
<my:myFields xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xmlns:my="http://schemas.microsoft.com/office/infopath/2003/myXSD/
    2004-05-09T20-16-51" xmlns:xd="http://schemas.microsoft.com/
    office/infopath/2003" 
xml:lang="en-us">
<my:field1>abc</my:field1> 
<my:signatures1>
  <my:signatures2>
.  .   

</my:signatures2>
</my:signatures1>
</my:myFields>

Note that the DigestValue, SignatureValue, and KeyInfo elements do not contain any values. These values are filled in by your code when the signature is computed. The signature template defines a digital signature for a set of signable data named "sig1" that allows only one signature, and signs the group specified by the XPath expression, /my:myFields. InfoPath stores the signature added to this set of signable data in the node specified by the following XPath expression.

/my:myFields/my:signatures1/my:signatures2

For an overview of how XML Signatures are encoded and stored, see An Introduction to XML Digital Signatures.

Adding a Signature with the SignWithKey and Sign Functions

The SignWithKey function adds a signature to an InfoPath form. An example of the SignWithKey function follows Table 1, which shows the arguments required for the SignWithKey function.

Table 1. Required Arguments for the SignWithKey Function

Argument

Description

CspType

An integer value that specifies the cryptographic service provider (CSP) type to use for calling the MSXML 5.0 createKeyFromCSP method. Accepted values are 1 for the PROV_RSA_FULL provider, or 13 for the PROV_DSS_DH provider. For more information on CSP types, see Cryptographic Provider Types.

KeyContainerName

The name of the key container.

Note
When requesting a digital certificate from a Windows Server 2003 Certificate Authority (CA), you can specify the key container name in the User specified key container name box on the Advanced Certificate Request page. By default, this value is an empty string.

TemplateFile

The name of the file that contains the signature template.

SignedFile

The name of the resulting signed file.

public static bool SignWithKey(int CspType, string KeyContainerName, 
    string TemplateFile, string SignedFile)
{
// Create variables for working with the form and signatures.
    MSXML2.IXMLDOMDocument3 xmldoc = new MSXML2.DOMDocument50Class();
    MSXML2.IXMLDigitalSignature digsig = 
        new MSXML2.MXDigitalSignature50Class();

    // The InfoPath form requires the preservation of white space.
    xmldoc.preserveWhiteSpace = true;

    // Set the W3C XML Digital Signatures namespace as the namespace
    // used in XPath expressions that specify digital signatures.
    xmldoc.setProperty("SelectionNamespaces", 
        "xmlns:sig='http://www.w3.org/2000/09/xmldsig'");
    
    // Load the signature template .xml file.
    if(!xmldoc.load(TemplateFile))
        Console.Out("Failed loading signature template file: " 
           + TemplateFile);

    // Retrieve the signature node.
    digsig.signature = xmldoc.selectSingleNode("//sig:Signature");

    // Create a key from the certificate and sign with it.
    try
    {
        MSXML2.IXMLDSigKey dsigKey = digsig.createKeyFromCSP(CspType, 
            "", KeyContainerName, 0);
        MSXML2.IXMLDSigKey signedKey = digsig.sign(dsigKey, 
            MSXML2.XMLDSIG_WRITEKEYINFO.CERTIFICATES | 
            MSXML2.XMLDSIG_WRITEKEYINFO.PURGE);
    }
    catch(Exception ex)
    {
        Console.Out("Could not sign template with key. \n" + ex.Message);
        return false;
    }

    // Saved the signed form to the file name specified in 
    // the SignedFile argument.
    xmldoc.save(SignedFile);
    return true;
}

The Sign function accepts arguments for the file that contains the signature template, the data to sign, and the name of the output file in which to save the signed data. In the following example, the Sign function calls the SignWithKey function to create a signature. The signature is created using the digital signature algorithms of the PROV_RSA_FULL general purpose CSP with a digital certificate that has a key container named "MyContainer".

public static bool Sign(string TemplateFile, string SignedFile)
{
    string KeyContainerName = "MyContainer";
    int PROV_RSA_FULL = 1;
    int PROV_DSS_DH = 13;

    // Sign using RSA certificate with "MyContainer" 
    // custom certificate key container name.
    return SignWithKey(PROV_RSA_FULL, KeyContainerName, 
       TemplateFile, SignedFile);
}

The following constraints apply to the Signature elements in the signature template:

  • The value of the Id attribute of the Signature element must contain the name of the set of signable data and the signature index in the following format:

    MySignature_SetOfSignableDataName_nnn

    where nnn is 000 for the index of the first signature, 001 for the index of the second signature, and so on. For example, MySignature_Sig1_000 is the Id value for the first digital signature added to the set of signable data named Sig1, followed by MySignature_Sig1_001 for the second signature added to Sig1.

  • The value of the Id attribute of the SignatureProperty element within each Signature element must have the same format and numbering scheme, as follows.

    MyComment_SetOfSignableDataName_nnn

  • The value of the Id attribute of the Signature element must be the same as the value of the Target attribute of the SignatureProperty element that is contained within the SignatureProperties node of the Object element.

  • The value of the Id attribute of the SignatureProperty element must be the same as the value of the URI attribute of the last Reference element contained within the Signature element, except that the leading # character is omitted.

Within the boundaries of these constraints, you can modify this code example to create co-signatures or counter-signatures for multiple signatures in a form.

Interoperability Between InfoPath and .NET Framework Digital Signatures Classes

Digital signatures that you create with InfoPath are compatible with signatures you create by using the digital signatures classes in the System.Xml namespace in both Microsoft .NET Framework 1.1 and 2.0. InfoPath can successfully verify signatures you create with the System.Xml signatures classes to sign all or part of an InfoPath form. Similarly, you can use the System.Xml signature verification classes to verify any signature you create with InfoPath.

One of the challenges of writing managed code to create and verify InfoPath digital signatures is that the SignedXml class provided by .NET Framework 1.1 and 2.0 does not support some attributes unique to MSXML 5.0. For example, the xml:lang attribute in the root node is one of the default attributes in an InfoPath form that raises an error during signing or causes an invalid result during verification. To see functions that show how to work around the differences in .NET Framework and MSXML 5.0, see the code example in Adding Digital Signatures Without Using the Digital Signatures Wizard.

The code examples in this section show how to use managed code to:

  • Verify the signatures InfoPath generates from an application created with managed code.

  • Create digital signatures for InfoPath forms with external applications. These signatures can be successfully verified by InfoPath.

  • Allow users to add a digital signature to an InfoPath form without using the Digital Signatures Wizard. This example also shows how to customize the OnSign event handler.

Verifying Digital Signatures in an InfoPath Form with .NET Framework 1.1 Managed Code

To use .NET Framework 1.1 managed code to verify the signature in the following example, your code must remove the xml:lang attribute from the root node of the document before the form is signed. To accomplish this, you can customize the OnSign event handler of the InfoPath form template by adding the following line of code.

thisXDocument.DOM.selectSingleNode("//my:myFields").attributes.
   removeNamedItem("xml:lang");

Note

This line of code assumes you are using C# and the Microsoft Office InfoPath 2003 Toolkit for Visual Studio .NET. To perform the same operation from Microsoft JScript without using the toolkit, simply replace thisXDocument with Xdocument .

The following C# code example shows how you can use .NET Framework 1.1 managed code to verify digital signatures in an InfoPath form. The name of the signature container node in this example is signatures2 .

using System;
using System.Drawing;
using System.Collections;
using System.Data;
using System.Security.Cryptography;
using System.Security.Cryptography.Xml;
using System.Security.Cryptography.X509Certificates;
using System.Runtime.InteropServices;
using System.Xml;

namespace VerifySignatures
{

class VerifySig
{
private void Verify(string SignedFileName)
{
            // Create a new XmlDocument object and load the signed file.
            XmlDocument currentDocument = new XmlDocument();
            currentDocument.PreserveWhitespace = true;
            currentDocument.Load(SignedFileName);

            // Get an XmlNodeList of all the signatures in the document.
            System.Xml.XmlNodeList theSignatures = 
                currentDocument.SelectNodes(
                "//*[local-name()='Signature']" );

            // Verify each signature in the document.
            Boolean fValidSignature = true;
            for (int j=0; j < theSignatures.Count; j++)
            {
               // Create a new SignedXML object.
               SignedXml signedXml = new SignedXml( currentDocument );

               // Create an XmlNamespaceManager object to resolve 
               // the necessary namespaces, and add the W3C XML Digital
               // Signatures namespace.
               XmlNamespaceManager nsmgr = 
                   new XmlNamespaceManager(currentDocument.NameTable);
               nsmgr.AddNamespace(
                   "sig", "http://www.w3.org/2000/09/xmldsig");
               
               // Get a reference to the X509Certificate data.
               System.Xml.XmlElement certElement = 
                   ((System.Xml.XmlElement) 
                   ((System.Xml.XmlElement)theSignatures[j])
                   .SelectSingleNode(
                   "sig:KeyInfo/sig:X509Data/sig:X509Certificate", 
                   nsmgr));

               // Get an RSA Public Key from the certificate.
               // See the KeyFromCert.cs file below for details
               System.Security.Cryptography.RSACryptoServiceProvider rsa = 
                   InfopathDSigVerify.KeyFromCert
                   .GetPublicKeyFromCertElement(certElement );
               
               //Load the next signature.
               signedXml.LoadXml(
                   (System.Xml.XmlElement)theSignatures[j]);

               //Verify the signature.
               fValidSignature = signedXml.CheckSignature(rsa);

               //If the signature isn't valid, break out.
               if (fValidSignature == false)
               {
                  break;
               }
            }

            // Report whether the file is valid or not.
            if (fValidSignature == false)
            {
                System.Windows.Forms.MessageBox.Show(
                    openFileDialog1.FileNames[i] +
                    " not validated." );
            }
            else
            {
                System.Windows.Forms.MessageBox.Show(
                    openFileDialog1.FileNames[i] +
                    " validated." );
            }
}

}
}

The previous code example depends on members from the KeyFromCert class in the InfopathDSigVerify namespace. The code for the KeyFromCert.cs is as follows.

using System;
using System.Xml;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Security.Cryptography.Xml;
using System.Security.Cryptography.X509Certificates;

namespace InfopathDSigVerify
{
public class KeyFromCert
{

    /// This function creates and returns an RSACryptoServiceProvider 
    /// object (containing only the public key) based on the supplied 
    /// XmlDSig X509Certificate element.
    public static RSACryptoServiceProvider GetPublicKeyFromCertElement(
        XmlElement certElement)
    {
        // Get an X509Certificate from the XML element.
        X509Certificate cert = CreateX509CertificateFromXMLElement(
            certElement );

        // Return the RSA public key from the certificate.
        return GetPublicKeyFromX509Certificate(cert);
    }

    /// <summary>
    /// This method takes an XML element which contains a base64-encoded 
    /// X509Certificate in ASN.1 format and loads it into a 
    /// System.Security.Cryptography.X509Certificates.X509Certificate 
    /// object.
    /// </summary>
    /// <param name="theCertElement">The XML element containing
    /// the encoded X509Certificate</param>
    /// <returns>A new
    /// System.Security.Cryptography.X509Certificates.X509Certificate 
    /// object</returns>
    private static X509Certificate CreateX509CertificateFromXMLElement(
        XmlElement theCertElement)
    {
    // Make sure the name of the element is "X509Certificate",
    / to confirm the user is making a good-faith effort to provide 
    / the proper data.
    if (theCertElement.LocalName != "X509Certificate")
    { 
        throw new System.Exception("Bad element name!");
    }

    // The text of the element should be a Base64 representation 
    // of the certificate, so load it as a string.
    String base64CertificateData = theCertElement.InnerText;

    // Remove any whitespace that may be cluttering up the data.
    base64CertificateData = base64CertificateData.Replace("\n", "" );
    base64CertificateData = base64CertificateData.Replace("\r", "" );
    base64CertificateData = base64CertificateData.Replace("\f", "" );
    base64CertificateData = base64CertificateData.Replace("\t", "" );
    base64CertificateData = base64CertificateData.Replace(" ", "" );

    // Convert the data to a byte array.
    byte[] certificateData = System.Convert.FromBase64String(
        base64CertificateData );

    // Create a new X509Certificate from the data.
    System.Security.Cryptography.X509Certificates.X509Certificate cert = 
        new System.Security.Cryptography.X509Certificates.X509Certificate(
        certificateData );

    //Return the result
    return cert;
   }

    /// <summary>
    /// This function creates and returns an RSACryptoServiceProvider 
    /// object (containing only the public key) based on the
    /// supplied X509Certificate object.
    /// </summary>
    /// <param name="x509">the X509Certificate object from which 
    /// to extract a public key.</param>
    /// <returns>A System.Security.Cryptography.RSACryptoServiceProvider
    /// object</returns>
    private static RSACryptoServiceProvider
        GetPublicKeyFromX509Certificate(X509Certificate x509)
    {
    // This code has been adapted from the KnowledgeBase article
    // 320602 HOW TO: Sign and Verify SignedXml Objects Using Certificates
    // http://support.microsoft.com/?id=320602

    RSACryptoServiceProvider rsacsp = null;
    uint hProv = 0;
    IntPtr pPublicKeyBlob = IntPtr.Zero;

    // Get a pointer to a CERT_CONTEXT from the raw certificate data.
    IntPtr pCertContext = IntPtr.Zero;
    pCertContext = (IntPtr)CertCreateCertificateContext(
        X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
        x509.GetRawCertData(),
        x509.GetRawCertData().Length);
        
    if (pCertContext == IntPtr.Zero)
    {
        Console.WriteLine("CertCreateCertificateContext failed: " 
            + Marshal.GetLastWin32Error().ToString());
        goto Cleanup;
    }
        
    if (!CryptAcquireContext(ref hProv, null, null, PROV_RSA_FULL, 0)) 
    { 
        if (!CryptAcquireContext(ref hProv, null, null, PROV_RSA_FULL,
            CRYPT_NEWKEYSET)) 
        { 
            Console.WriteLine("CryptAcquireContext failed: " 
                + Marshal.GetLastWin32Error().ToString());
            goto Cleanup;
        }
    }

    // Get a pointer to the CERT_INFO structure.
    // It is the 4th DWORD of the CERT_CONTEXT structure.
    //
    //    typedef struct _CERT_CONTEXT {
    //        DWORD         dwCertEncodingType;
    //        BYTE*         pbCertEncoded;
    //        DWORD         cbCertEncoded;
    //        PCERT_INFO    pCertInfo;
    //        HCERTSTORE    hCertStore;
    //    } CERT_CONTEXT,  *PCERT_CONTEXT;
    //    typedef const CERT_CONTEXT *PCCERT_CONTEXT;
    //
    IntPtr pCertInfo = (IntPtr)Marshal.ReadInt32(pCertContext, 12);

    // Get a pointer to the CERT_PUBLIC_KEY_INFO structure.
    // This structure is located starting at the 57th byte
    // of the CERT_INFO structure.
    // 
    //    typedef struct _CERT_INFO {
    //        DWORD                       dwVersion;
    //        CRYPT_INTEGER_BLOB          SerialNumber;
    //        CRYPT_ALGORITHM_IDENTIFIER  SignatureAlgorithm;
    //        CERT_NAME_BLOB              Issuer;
    //        FILETIME                    NotBefore;
    //        FILETIME                    NotAfter;
    //        CERT_NAME_BLOB              Subject;
    //        CERT_PUBLIC_KEY_INFO        SubjectPublicKeyInfo;
    //        CRYPT_BIT_BLOB              IssuerUniqueId;
    //        CRYPT_BIT_BLOB              SubjectUniqueId;
    //        DWORD                       cExtension;
    //        PCERT_EXTENSION             rgExtension;
    //    } CERT_INFO, *PCERT_INFO;
    // 
    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 
    // into CryptImportPublicKeyInfoEx.
    // 
    uint hKey = 0;
    if (!CryptImportPublicKeyInfoEx(hProv, X509_ASN_ENCODING | 
        PKCS_7_ASN_ENCODING, pSubjectPublicKeyInfo, 0, 0, 0, ref hKey)) 
    {
        Console.WriteLine("CryptImportPublicKeyInfoEx failed: " 
            + Marshal.GetLastWin32Error().ToString());
        goto Cleanup;
    }

    // Now that the key is imported into a key container use
    // CryptExportKey to export the public key to the PUBLICKEYBLOB
    // format.
    // First get the size of the buffer needed to hold the 
    // PUBLICKEYBLOB structure.
    // 
    uint dwDataLen = 0;
    if (!CryptExportKey(hKey, 0, PUBLICKEYBLOB, 0, 0, ref dwDataLen))
    {
        Console.WriteLine("CryptExportKey failed: " 
            + Marshal.GetLastWin32Error().ToString());
        goto Cleanup;
    }

    // Then export the public key into the PUBLICKEYBLOB format.
    pPublicKeyBlob = Marshal.AllocHGlobal((int)dwDataLen);
    if (!CryptExportKey(hKey, 0, PUBLICKEYBLOB, 0, 
        (uint)pPublicKeyBlob.ToInt32(), ref dwDataLen))
    {
        Console.WriteLine("CryptExportKey failed: " 
            + Marshal.GetLastWin32Error().ToString());
        goto Cleanup;
    }

    // The PUBLICKEYBLOB has the following format:
    //        BLOBHEADER blobheader;
    //        RSAPUBKEY rsapubkey;
    //        BYTE modulus[rsapubkey.bitlen/8];
    // 
    // Which can be expanded to the following:
    // 
    //        typedef struct _PUBLICKEYSTRUC {
    //            BYTE   bType;
    //            BYTE   bVersion;
    //            WORD   reserved;
    //            ALG_ID aiKeyAlg;
    //        } BLOBHEADER, PUBLICKEYSTRUC;
    //        typedef struct _RSAPUBKEY {
    //            DWORD magic;
    //            DWORD bitlen;
    //            DWORD pubexp;
    //        } RSAPUBKEY;
    //        BYTE modulus[rsapubkey.bitlen/8];

    // Get the public exponent.
    // The public exponent is located in bytes 17 through 20 of the 
    // 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. The bit length of the modulus is at bytes 
    // 13 through 17 of the PUBLICKEYBLOB.
    int BitLength = Marshal.ReadInt32(pPublicKeyBlob, 12);
    
    // Get the modulus. The modulus starts at the 21st byte of the 
    // PUBLICKEYBLOB structure and is BitLengh/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 exponent into an RSAParameters object.
    RSAParameters rsaparms = new RSAParameters();
    rsaparms.Exponent = Exponent;
    rsaparms.Modulus = Modulus;

    // Import the modulus and exponent into an RSACryptoServiceProvider
    // object via the RSAParameters object.
    rsacsp = new RSACryptoServiceProvider();
    rsacsp.ImportParameters(rsaparms);

    Cleanup:
        
        if (pCertContext != IntPtr.Zero)
            CertFreeCertificateContext(pCertContext);
        
        if (hProv != 0)
            CryptReleaseContext(hProv, 0);
        
        if (pPublicKeyBlob != IntPtr.Zero)
            Marshal.FreeHGlobal(pPublicKeyBlob);
        
        return rsacsp;
}

    private const int X509_ASN_ENCODING= 0x00000001;
    private const int PKCS_7_ASN_ENCODING =0x00010000;
    private const int PROV_RSA_FULL = 1;
    private const int PUBLICKEYBLOB = 0x6;
    private const int CRYPT_NEWKEYSET = 0x00000008;

    // Import statements for calls to Crypto API and Win32 API methods.
    [DllImport("Crypt32.DLL", EntryPoint="CertCreateCertificateContext",
        SetLastError=true,
        CharSet=CharSet.Unicode, ExactSpelling=false,
        CallingConvention=CallingConvention.StdCall)]
    private static extern IntPtr CertCreateCertificateContext(
         int dwCertEncodingType,
         byte[] pbCertEncoded,
         int cbCertEncoded );

    [DllImport("Crypt32.DLL", EntryPoint="CryptAcquireContextU",
        SetLastError=true,
        CharSet=CharSet.Unicode, ExactSpelling=false,
        CallingConvention=CallingConvention.StdCall)]
    private static extern bool CryptAcquireContext(
         ref uint phProv, string szContainer,
         string szProvider,
         int dwProvType,
         int dwFlags );

    [DllImport("Crypt32.DLL", EntryPoint="CryptImportPublicKeyInfoEx",
        SetLastError=true,
        CharSet=CharSet.Unicode, ExactSpelling=false,
        CallingConvention=CallingConvention.StdCall)]
    private static extern bool CryptImportPublicKeyInfoEx(
        uint hCryptProv,
        uint dwCertEncodingType,
        IntPtr pInfo,
        int aiKeyAlg,
        int dwFlags,
        int pvAuxInfo,
        ref uint phKey);

    [DllImport("Advapi32.DLL", EntryPoint="CryptExportKey",
        SetLastError=true,
        CharSet=CharSet.Unicode, ExactSpelling=false,
        CallingConvention=CallingConvention.StdCall)]
    private static extern bool CryptExportKey(
        uint hKey,
        uint hExpKey,
        int dwBlobType,
        int dwFlags,
        uint pbData,
        ref uint pdwDataLen);

    [DllImport("Crypt32.DLL", EntryPoint="CertFreeCertificateContext", 
        SetLastError=true,
        CharSet=CharSet.Unicode, ExactSpelling=false,
        CallingConvention=CallingConvention.StdCall)]
    private static extern bool CertFreeCertificateContext(
        IntPtr pCertContext);

    [DllImport("Advapi32.DLL", EntryPoint="CryptReleaseContext", 
        SetLastError=true,
        CharSet=CharSet.Unicode, ExactSpelling=false,
        CallingConvention=CallingConvention.StdCall)]
    private static extern bool CryptReleaseContext(
        uint hProv,
        int dwFlags);
   }
}

Verifying Digital Signatures in an InfoPath Form with .NET Framework 2.0 Managed Code

Note

The following code example was developed with Microsoft Visual Studio 2005 Beta and the Microsoft .NET Framework 2.0 Beta.

You can verify digital signatures in InfoPath forms from external applications created with .NET Framework 2.0 (Whidbey) managed code. Using .NET Framework 2.0 classes to do this is shorter and simpler than using .NET Framework 1.1 managed code. Just as with the previous examples that use .NET Framework 1.1 classes, your code must remove the xml:lang attribute from the form XML before it is signed.

using System.Web;
using System.Web.Services;
using System.Web.Services.Protocols;
using System.Xml;
using System.Security;
using System.Security.Cryptography.Xml;
using System.Security.Cryptography.X509Certificates;
using System.Security.Cryptography;

[WebServiceBinding(ConformanceClaims=WsiClaims.BP10,
       EmitConformanceClaims = true)]
[WebService(Namespace = "http://www.microsoft.com/office/infopath/dsigs")]
public class Service  : System.Web.Services.WebService {

    [WebMethod]
    public bool XmlDsigVerify(string xmlDocString) {
    // The parameter is a string to preserve whitespace.

        // Load the XML string into a DOM document.
        XmlDocument xmlDoc = new XmlDocument();
        xmlDoc.PreserveWhitespace = true;
        xmlDoc.LoadXml(xmlDocString);
        
        // Prepare the signedXML object context and identify 
        // all the signatures in the document.
        bool retVal = true;
        SignedXml signed = new SignedXml(xmlDoc);
        XmlNodeList nodeList = xmlDoc.GetElementsByTagName("Signature");

        foreach(XmlElement e in nodeList)
        {
            signed.LoadXml((e));
            XmlNodeList certificates = 
                e.GetElementsByTagName("X509Certificate");
            X509CertificateEx cert = 
                CreateWSEX509CertificateFromXMLElement(
                (XmlElement)certificates[0]);
            RSACryptoServiceProvider rsacsp =
                (RSACryptoServiceProvider)cert.PublicKey.Key;
            // Return value = the Signature Hash is valid && Certificate 
            // used is trusted. If the certificate is not trusted
            // return false as InfoPath does.
            retVal = retVal && signed.CheckSignature(rsacsp) 
                && cert.Verify();
        }

        return retVal;
    }

    private static X509CertificateEx 
        CreateWSEX509CertificateFromXMLElement(
        System.Xml.XmlElement theCertElement)
    {

        // The text of the element should be a Base64 representation of
        // the certificate, so load it as a string.
        string base64CertificateData = theCertElement.InnerText;

        // Remove any whitespace that may be cluttering up the data.
        base64CertificateData = base64CertificateData.Replace("\n", "");
        base64CertificateData = base64CertificateData.Replace("\r", "");
        base64CertificateData = base64CertificateData.Replace("\f", "");
        base64CertificateData = base64CertificateData.Replace("\t", "");
        base64CertificateData = base64CertificateData.Replace(" ", "");

        // Convert the data to a byte array
        byte[] certificateData = System.Convert.FromBase64String(
           base64CertificateData);

        // Create a new X509Certificate from the data.
        X509CertificateEx cert = new X509CertificateEx(certificateData);

        // Return the result.
        return cert;
    }
}

Digitally Signing an InfoPath Form with Managed Code

To use managed code and the digital signatures support provided by .NET Framework to create a signature in an InfoPath form, you must:

  • Use the members of the SignedXml class to create and verify signatures.

  • Add the signature template in the correct node of the form. To create a signature template, you can capture the XML for the Signature object that is returned by calling the Create method of the Signatures collection. A usable signature adds the correct value in DigestValue elements contained within each Reference element, adds the correct information to the KeyInfo element, and adds the correct value for the SignatureValue element.

  • Add the name of the set of signable data and the index of the signature to the Id attribute of the Signature element in the following format:

    MySignature_SetOfSignableDataName_nnn

    where nnnis 000 for the index of the first signature, 001 for the index of the second signature, and so on. For example, Id="MySignature_TestSignature_000" is the value of the Id attribute for the first signature added to the "TestSignature" set of signable data.

  • Remove the xml:lang attribute from the root node of the form. To see functions that address other incompatibilities, see the code example in Adding Digital Signatures Without Using the Digital Signatures Wizard. The example shows methods you can use to convert between documents compatible with MSXML 5.0 and the System.Xmlnamespace in .NET Framework.

  • Use a certificate to sign the form that allows the private key to be exported.

The signature template, SigTemplate.xml, which is used in the following examples, should look like the following.

<Signature xmlns="http://www.w3.org/2000/09/xmldsig" 
     Id="MySignature_group1_000">
<SignedInfo>
    <CanonicalizationMethod Algorithm="http://www.w3.org/TR/
        2001/REC-xml-c14n-20010315" /> 
    <SignatureMethod Algorithm="http://www.w3.org/2000/09/
        xmldsigrsa-sha1" /> 
    <Reference URI="">
       <Transforms>
          <Transform Algorithm=
              "http://www.w3.org/2000/09/xmldsigenveloped-signature" /> 
          <Transform Algorithm=
              "http://www.w3.org/TR/1999/REC-xslt-19991116">
            <stylesheet 
                "http://www.w3.org/1999/XSL/Transform" version="1.0">
                <template match="/">
                     <element name="SignedData" namespace="http://
                        schemas.microsoft.com/office/infopath/
                          2003/SignatureProperties">
                     <apply-templates select="." mode="skip" /> 
                     </element>
                </template>
                <template match="@*|node()" priority="100" mode="skip">
                     <apply-templates select="@*|node()" mode="skip" /> 
                </template>
                           <template match="/my:myFields/
                              my:group1" priority="200" mode="skip" 
                                  xmlns:my="http://schemas.microsoft.com/
                                  office/infopath/2003/myXSD/2004-05-
                                   10T05:27:09">
                     <apply-templates select="." mode="copy" /> 
                </template>
                <template match="@*|node()" priority="100" mode="copy">
                     <copy>
                        <apply-templates select="@*|node()" mode="copy" /> 
                     </copy>
                </template>
                        <template match="/my:myFields/my:signatures1/
                         my:signatures2/node()" priority="200" mode="copy" 
                          xmlns:my="http://schemas.microsoft.com/
                          office/infopath/2003/myXSD/2004-05-
                          10T05:27:09" /> 
                     </stylesheet>
            </Transform>
      <Transform Algorithm=
          "http://www.w3.org/TR/2001/REC-xml-c14n-20010315" /> 
       </Transforms>
  <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsigsha1" /> 
  <DigestValue /> 
</Reference>
<Reference URI="MyComment_group1_000" 
    Type="http://www.w3.org/2000/09/xmldsigSignatureProperties">
    <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsigsha1" /> 
    <DigestValue /> 
</Reference>
  </SignedInfo>
  <SignatureValue /> 
  <KeyInfo /> 
  <Object>
<SignatureProperties>
    <SignatureProperty Id="MyComment_group1_000"
        Target="MySignature_group1_000">
         <Comment xmlns="http://schemas.microsoft.com/
            office/infopath/2003/SignatureProperties">comment</Comment> 
         <sp:NonRepudiation xmlns:sp="http://schemas.microsoft.com/
           office/infopath/2003/SignatureProperties">
             <sp:UntrustedSystemDateTime>2004-05-
               10T05:34:32Z</sp:UntrustedSystemDateTime> 
             <sp:SystemInformation>
                <sp:OperatingSystem>5.1.2600</sp:OperatingSystem> 
                <sp:Office>11.0.6353</sp:Office> 
                <sp:InfoPath>11.0.6353</sp:InfoPath> 
            </sp:SystemInformation>
                      <sp:ScreenInformation>
<sp:NrOfMonitors>1</sp:NrOfMonitors> 
<sp:PrimaryMonitor>
            <sp:Width Unit="px">1024</sp:Width> 
             <sp:Height Unit="px">768</sp:Height> 
             <sp:ColorDepth Unit="bpp">16</sp:ColorDepth> 
         </sp:PrimaryMonitor>
      </sp:ScreenInformation>
      <sp:SolutionInformation>
             <sp:SolutionFingerprint>73ab1c4d72ffbb1bc7c4e7b8805c96
                </sp:SolutionFingerprint> 
             <sp:CurrentView>View 1</sp:CurrentView> 
      </sp:SolutionInformation>
      <sp:ScreenDumpPNG>PNG image data removed</sp:ScreenDumpPNG> 
      </sp:NonRepudiation>
     </SignatureProperty>
  </SignatureProperties>
 </Object>
</Signature>

The form that contains the template has digital signatures defined for a set of signable data and a signatures container node with the following structure.

<my:signatures1>
    <my:signatures2></my:signatures2>
</my:signatures1>

The name of the set of signable data is group1 and it is specified by the XPath expression /my:myFields/my:group1 .

The following C# code example adds a digital signature compatible with InfoPath to the form.

using System;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Security.Cryptography.Xml;
using System.Xml;
using System.Runtime.InteropServices;

namespace SystemXmlDigSig
{

class Sign
{

public static bool Sign(string TemplateFile, string SignedFile)
{
XmlDocument xmlDocument = new XmlDocument();
xmlDocument.PreserveWhitespace=true;

// Load the unsigned document
xmlDocument.Load(TemplateFile);

// Get the certificate named "Exportable Key" which must have an 
// exportable private key. The certificate should be located
// in the default "My" store.
uint _certContext = GetCertificate("My", "Exportable Key");

// Sign the data.
ManagedSign(xmlDocument, _certContext, SignedFile);

return true;
}

internal static void ManagedSign(XmlDocument xmlDocument, 
    uint _certContext, string SignedFile)
{
// Add an XmlNamespaceManager to resolve all necessary 
     namespaces
XmlNamespaceManager nsmgr = 
    new XmlNamespaceManager(xmlDocument.NameTable);
nsmgr.AddNamespace("my", 
    "http://schemas.microsoft.com/office/infopath/2003
       /myXSD/2004-05-02T23:35:25");

RSACryptoServiceProvider rsacsp = 
    RSACryptoServiceProviderFromCertContext(
    new IntPtr((int)_certContext));
if(rsacsp == null)
    return;

    // Create the certificate from context.
    X509Certificate myCert = new X509Certificate((IntPtr)_certContext);
    
    // Load the signature template to create the Signature node.
    XmlDocument sigTemplate = new XmlDocument();
    sigTemplate.Load("SigTemplate.xml");

    // Add Signature node to document.
    XmlNode signatureContainer = xmlDocument.SelectSingleNode(
        "//*[local-name(.) = 'signatures2']");
    signatureContainer.AppendChild(xmlDocument.ImportNode(
        sigTemplate.DocumentElement, true));

    // Create the SignedXml message.
    SignedXml signedXml = new SignedXml(xmlDocument);

    // Load the signature template into SignedXml
    signedXml.LoadXml(sigTemplate.DocumentElement);

    // Set the signing key
    RSA key = RSA.Create();
    key.ImportParameters(rsacsp.ExportParameters(true));
    signedXml.SigningKey = key;

    // Add KeyInfo to Signature element
    KeyInfo keyInfo = new KeyInfo();
    keyInfo.AddClause(new KeyInfoX509Data(myCert));
    signedXml.KeyInfo = keyInfo;

    // Fix signature Id: "MySignature_" + sdbName + "_" + SignatureIndex
    signedXml.Signature.Id="MySignature_group1_000";

    // Compute the signature.
    signedXml.ComputeSignature();

    // Get the XML representation of the signature.
    XmlElement xmlSignature = signedXml.GetXml();

    // Reposition the nodes so that the result is an enveloped signature.
    XmlNode n = xmlDocument.ImportNode(xmlSignature, true);
    XmlNode signatures = xmlDocument.SelectSingleNode(
        "//*[local-name(.) = 'signatures2']");
    if (signatures == null)
        signatures = xmlDocument.DocumentElement;
    signatures.ReplaceChild(n,signatures.FirstChild);
 
    // Save the signed document to the name specified by SignedFile.
    xmlDocument.PreserveWhitespace=true;
    xmlDocument.Save(SignedFile);

}

public static uint GetCertificate(
    string StoreName, string CertificateName)
{
    uint _certContext = uint.MaxValue;
    uint hCertStore = Crypt32.CertOpenSystemStore(0, StoreName);

    if (hCertStore == 0)
    {
        Console.Write("CertOpenSystemStore failed: " 
            + Marshal.GetLastWin32Error().ToString());
        return _certContext;
    }
    uint pCertContext = Crypt32.CertEnumCertificatesInStore(
        hCertStore, (uint)0);

    while(pCertContext != 0) 
    {
        X509Certificate x509 = new X509Certificate((IntPtr)pCertContext);
        _certContext = 
            Crypt32.CertDuplicateCertificateContext(pCertContext);
        if(x509.GetName().IndexOf(CertificateName)>0)
            break;
        pCertContext = Crypt32.CertEnumCertificatesInStore(
            hCertStore, pCertContext);
    }

    return _certContext;
}

public static 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)
    {
// Display error if call to CertGetCertificateContextProperty method
// fails.
Console.Write("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)
        {
        // Display error if call to CertGetCertificateContextProperty
        // method fails.
        Console.Write("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)
       {
        // Display error if call to CertGetCertificateContextProperty
        // method fails.
        Console.Write("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)
        {
            Console.Write(ex.Message);
        }
    }
              }
              return null;
}
}

/// <summary>
/// Crypt32 class for calls to Crypto API and Win32 API methods.
/// </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);
}
}

Adding Digital Signatures Without Using the Digital Signatures Wizard

By default, InfoPath requires users to step through the Digital Signatures Wizard user interface to select a digital certificate and make other choices to sign form data. In certain scenarios, you may want to override the Digital Signatures Wizard and fully automate the process of digitally signing form data. In such a scenario, the user can just click a button to run form code that automatically selects the correct certificate and signs the data without requiring any other user intervention.

The following example is an InfoPath form template that allows users to sign data in an InfoPath form without using the Digital Signatures Wizard. The example was created with the Microsoft Office InfoPath 2003 Toolkit for Visual Studio .NET. You can use this example to set up the form code to perform the following steps:

  1. Use the InfoPath digital signatures object model to get the signature template.

  2. Call a function that adds the template to the form.

  3. Calculate the KeyInfo data from the certificate that is used to sign the data.

  4. Add the key information in the correct node.

  5. Compute the signature.

  6. Add the digest values and the signature value to the correct nodes.

    Note

    The certificate that is used must allow the private key in the "My" store to be exported with an "Exportable Key" name.

You must enable digital signatures for a specific set of data in the form that is to be signed. In the following example, the set of signable data is named TestSignature .

using System;
using System.IO;
using System.Security.Cryptography;
using Cryptography=System.Security.Cryptography.Xml;
using System.Text;
using System.Xml;
using System.Security.Cryptography.X509Certificates;
using System.Runtime.InteropServices;

using Microsoft.Office.Interop.InfoPath.SemiTrust;

// Office integration attribute. Identifies the 
       startup class for the 
// form. Do not modify.
[assembly: System.ComponentModel.
     DescriptionAttribute("InfoPathStartupClass, Version=1.0, 
     Class=DigSigOverride.DigSigOverride")]

namespace DigSigOverride
{

public class DigSigOverride
{
private const int CERT_KEY_PROV_INFO_PROP_ID = 2;

// The following function handler is created by Microsoft 
     Office InfoPath.
// Do not modify the type or number of arguments.
[InfoPathEventHandler(EventType=InfoPathEventType.OnSign)]
public void OnSign(SignEvent e)
{
// The OnSign handler can be customized only in fully trusted
// form templates.
    uint _certContext = GetCertificate("My", "Exportable Key");

    // The Signature object contains the signature template.
    Signature signature = e.SignedDataBlock.Signatures.Create();

    // Call the ManagedSign function to digitally sign using 
    // .NET Framework classes.
    ManagedSign(signature.SignatureBlockXmlNode, _certContext);
    e.ReturnStatus = true;

}

internal void ManagedSign(IXMLDOMNode signatureTemplateNode, 
    uint _certContext)
{
    string signatureContainerXpath = "//*[local-name(.) = 'signatures2']";

    // Call function to strip out all the xml:* attributes used by MSXML5,
    // which conflict with System.Xml.
    PrepareDOMForSystemXmlAndMsxmlDifferences();

    // Convert MSXML signature template nodes to System.Xml nodes.
    XmlElement signatureTemplate = 
        (XmlElement)ConvertNodeToSystemXml(signatureTemplateNode);

    // Convert form to System.Xml document.
    XmlDocument documentToSign = 
        ConvertDocumentToSystemXml(thisXDocument.DOM);

    // Select the signature container node.
    XmlNode signatureContainer = 
        documentToSign.SelectSingleNode(signatureContainerXpath);

    // Add the blank signature template to the signature container node.
    signatureContainer.AppendChild(
        documentToSign.ImportNode(signatureTemplate, true));

    // Create the SignedXml message using the signature template.
    Cryptography.SignedXml signedXml = 
        new Cryptography.SignedXml(documentToSign);
    signedXml.LoadXml(signatureTemplate);

    // Create the key using the selected certificate context.
    X509Certificate cert = new X509Certificate((IntPtr)_certContext);
    RSACryptoServiceProvider rsacsp = 
        RSACryptoServiceProviderFromCertContext(
        new IntPtr((uint)_certContext));
    RSA key = RSA.Create();
    key.ImportParameters(rsacsp.ExportParameters(true));
    signedXml.SigningKey = key;
    
    // Add the KeyInfo data.
    Cryptography.KeyInfo keyInfo = new Cryptography.KeyInfo();
    keyInfo.AddClause(new Cryptography.KeyInfoX509Data(cert));
    signedXml.KeyInfo = keyInfo;

    // Add the signature ID attribute.
    signedXml.Signature.Id="MySignature_TestSignature_000";

    // Compute the signature.
    signedXml.ComputeSignature();

    // Get the XML representation of the signature, convert it to MSXML,
    // and then add it to signature container node of the document.
    XmlElement xmlSignature = signedXml.GetXml();
    IXMLDOMNode xmlSignatureNode = ConvertNodeToMsxml(xmlSignature);
    IXMLDOMNode signatureContainerNode = 
        thisXDocument.DOM.selectSingleNode(signatureContainerXpath);
    signatureContainerNode.appendChild(xmlSignatureNode);

}

/// <summary>
/// This function strips out all of the xml:* attributes in the document. 
/// System.Xml's implementation of digital signatures
/// cannot consume these attributes used by MSXML5.
/// </summary>
internal void PrepareDOMForSystemXmlAndMsxmlDifferences()
{
    IXMLDOMNodeList xmlNodes = 
        thisXDocument.DOM.selectNodes(
        "//@*[namespace-uri(.)='http://www.w3.org/XML/1998/namespace']");
    ((IXMLDOMSelection)xmlNodes).removeAll();
}

internal XmlNode ConvertNodeToSystemXml(IXMLDOMNode node)
{
    string xmlAsString = node.xml;
    XmlDocument document = new XmlDocument();
    document.PreserveWhitespace = true;
    document.LoadXml(xmlAsString);
    return document.DocumentElement;
}

internal IXMLDOMNode ConvertNodeToMsxml(XmlNode node)
{
    string xmlAsString = node.OuterXml;
    IXMLDOMDocument document = thisXDocument.CreateDOM();
    document.preserveWhiteSpace = true;
    document.loadXML(xmlAsString);
        
    return document.documentElement;
}

internal XmlDocument ConvertDocumentToSystemXml(IXMLDOMDocument dom)
{
    StringReader stringReader = new StringReader(dom.xml);
    XmlTextReader xmlReader = new XmlTextReader(stringReader);
    xmlReader.Normalization = true;
    xmlReader.WhitespaceHandling = WhitespaceHandling.All;
    XmlDocument document = new XmlDocument();
    document.PreserveWhitespace = true;
    document.Load(xmlReader);

    return document;
}
}

// The GetCertificate method, RSACryptoServiceProviderFromCertContext 
// method, and Crypt32 class from the code example in the "Digitally 
// Sign an InfoPath Form Using Managed Code" section of this article must
// be added here.
}

Important

For this example to work, you must add the GetCertificate method, the RSACryptoServiceProviderFromCertContext method, and the Crypt32 class from the code example in Digitally Signing an InfoPath Form with Managed Code.

Conclusion

The examples in this article demonstrate how to use managed code to create external applications that verify and add digital signatures to data in InfoPath forms. The examples demonstrate the use of managed code that works with MSXML 5.0 or .NET Framework. You can see how to create applications and set up your InfoPath form code so the digital signature processes are compatible. The examples also show you how to write code that allows users to add digital signatures to data in an InfoPath form without using the Digital Signatures Wizard. With these examples, you can learn to more efficiently manage the digital signature process for your form data and form users.

Additional Resources

For more information, see the following: