Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / security / encryption

Encrypting InfoPath form Data

5.00/5 (2 votes)
3 Oct 2013CPOL9 min read 21.9K   493  
A neat library which can be used to easily add encryption for InfoPath form data.

Updates

03/10/2013 - Ok so I found a small bug that I managed to sneak in when I copied the source out of my library to make the example. It was causing the encyption to stop at the first "Group" node encountered; that is fixed. Also I seem to have forgotten to erase my byte array from InternalDecryptNode which means unless you followed my adivce to write your own as stock the decyption will fail; also fixed.

I have also added a second example for download (which I will write about later, maybe a second article). The example used a button click to Encryp/Decrypt the form and asks for password entry. The password is entered using a windows form so it is masked nicely. (No wingdings here :p).

A note on example 2: There is no error handling at present for incorrect passord entry. This will be added at a later date, but is fairly trivial if you need to do it yourself.

Introduction

So my first article... be gentle :p

In the following article I will detail the Cryptography library I have built for use with InfoPath forms. I will provide the full source code as well for those who wish to use it or modify it. However the usual rules apply; don't redistibute without due credit blahdy, blah...

The library does of course require some coding, however it is minimal and well documented in the article.

Background

Recently a colleague who is responsible for authoring InfoPath forms for our company was tasked with writing a staff appraisal form.

First off; we all know that InfoPath forms are simply XML (plain text) files. Which in theory means anyone with access to the form is able to read the contents with or without InfoPath.

Secondly; the form was to be sent over email which would mean that the form data is stored in the email system at both ends and therefore readily available for anyone who wanted to/knew where to look.

At first the default response from IT was of course... "No it can't be done... there is always away around it".

Your intrepid author thinks otherwise...

Of course given the right knowledge, equipment, and determination there are very few security measures that can't be bypassed in time. You could go from basic encoding of text right the way through to the most sophisticated encryption known to man. In the end your choice should reflect the importance of the security, amount of time it takes to implement, and the likely level of expertise of those malicious folk trying to undo your work.

In our case the data is not of vital importance to anyone except staff members and the average hacking skill of our staff members is er... low.

To my mind this means simple obfuscation should really be sufficient. However as always I like to go above and beyond for my own learning, as well as the cause.

So... behold my creation!

Using the Library

InfoPath forms have two main parts to them; the designer, and code behind.

Many developers simply use the designer for all their needs, however ours go a bit beyond that, so we need to use code behind. There are actually various ways which could be used to implement the security measures provided by this library, however I will concentrate on the following:

A form submitted by email which cannot be saved by the author and can only be opened by authorised users

Note: You will need Outlook setup in order to follow these instructions. If you do not have Outlook or don't want to use it with this article then you can just leave off step 2 to see the encryption working.

Update: You can now try example 2 to see the encryption working on a button press.

The Form Design

1. So let us begin with a blank infopath form. Then add some fields and a button.

Image 1

Note: All fields have a dataType of Text (string) except Number which is Whole Number (integer).

2. You will need to set up a data connection to submit the form to email called "Email Submit".

3. Set up the AuthorisedUsers role.

For now we will not actually specify any users so anyone opening the form will be authorised. If you want to learn more about user roles look here.

Image 2

 

Image 3

4. Next we set the button to Submit the form:

Image 4

Then hit "Submit Options..." and set the following options:

Image 5

5. Finally hit the "Edit Code..." button and after loading VSTA you will be ready to get started with the coding.

Note: If not already on C# by default you will need to set this under "Tools" > "Form Options" > "Programming", before hitting the "Edit Code..." button.

The Code

The code used for submitting the form comes in twoparts.

1. Add a reference to the library.

2. Copy the following code into FormCode.cs (replace all existing code).

C#
using Microsoft.Office.InfoPath;
using System;
using System.Windows.Forms;
using System.Xml;
using System.Xml.XPath;
using mshtml;
using InfoPathHelpers;
 
namespace CryptographyExample
{
    public partial class FormCode
    {
        public void InternalStartup()
        {
            EventManager.FormEvents.Submit += new SubmitEventHandler(FormEvents_Submit);
 
            if (!New && UserRole == "AuthorisedUsers")
             {
                 // If the user opening the form is an authorised user then decrypt all data.
                Cryptography.DecryptDocument(this, "key", true);
             }
        }
 
        public void FormEvents_Submit(object sender, SubmitEventArgs e)
        {
            Cryptography.EncryptDocument(this, "key", true);
 
            try
            {
                DataConnections["Email Submit"].Execute();
                e.CancelableArgs.Cancel = false;
            }
            catch (System.Net.WebException ex)
            {
                Cryptography.DecryptDocument(this, "key", true);
 
                if (!ex.Message.Contains("The form was not submitted because the action was canceled."))
                {
                    // If the operation was not cancelled then show the user an error message.
                    MessageBox.Show("The form could not be submitted due to the following error:\r\n\r\n" +
                        ex.Message, "Submission Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                }
            }
        }
    }
}

Testing the Form

Publish the form open a copy to see it in action. Or just preview it, but you will not be able to test the decryption this way.

A few notes:

1. When publishing the form it will require Full or Domain level trust. If you publish it to a shared folder it will open fine. Otherwise you must sign it with a certificate and set Full Trust.

2. You should notice that if you fill in the form and press Submit, all fields are encrypted... except the number field. This is because encyption creates a string and the number field will be invalid unless it is a number. The way to avoid this is to set the field to a string instead. Of course you then lose validation so you will have to validate the field manually. I have also created a library for this which I hope to write about in a later article.

The Library

Ok, the library itself is fairly well documented through comments and xml documentation. However I will outline the core principles below:

Encryption

The encyption is fairly standard and could easily be substituted for your own preferred method. But for the record here is my go at it.

C#
private static void InternalEncryptNode(XPathNavigator node, string key)
{
    var SymmetricAlgorithm = new RijndaelManaged();
    // Make up your own Byte[].
    var DerivedByteArray = new Rfc2898DeriveBytes(key, new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 });
    SymmetricAlgorithm.Key = DerivedByteArray.GetBytes(SymmetricAlgorithm.KeySize >> 3);
    SymmetricAlgorithm.IV = DerivedByteArray.GetBytes(SymmetricAlgorithm.BlockSize >> 3);
    var Buffer = new MemoryStream();
    byte[] NodeValueBytes = Encoding.UTF8.GetBytes(node.Value);
    using(var CryptoStream = new CryptoStream(Buffer, SymmetricAlgorithm.CreateEncryptor(), CryptoStreamMode.Write))
    {
        CryptoStream.Write(NodeValueBytes, 0, NodeValueBytes.Length);
    }
    node.SetValue(Convert.ToBase64String(Buffer.ToArray()));
}
 

 

This code just uses the RijndaelManaged algorithm to encrypt the value of the node and converts the result to Base64 before setting it back as the value on the node.

Walking the Node Tree

The meat and potatoes of the library is really this bit. How do we know which bits of the xml to encrypt?

Well to make it easy to use for devs I decided to make it as automated as possible. To this effect you can happily encrypt all fields with a single call to EncryptDocument(this, "key");

Alternatively you can specify a specific node or set of nodes to encrypt. At present it will actually encrypt all elements and attibutes found in the document. You can also specify (by name) any nodes you specifically do not want to encrypt.

Note: At the time of writing I intend to add flags to the method to specify such things as IgnoreAttributes, IsSchemaAware etc. to make it more customisable. But at present you will have to manually specify attibutes that should not be encrypted if you really want to.

So the code to walk the tree is as follows.

private static void ProcessAllNodes(XPathNavigator rootNode, List<string> nodesToIgnore, ProcessNodeDelegate processNode)
{
    if (nodesToIgnore == null)
    {
        nodesToIgnore = new List<string>();
    }
 
    // If the rootNode is null or has no children there is nothing to do.
    if (!rootNode.HasChildren && NodeIsNullOrEmpty(rootNode))
    {
        return;
    }
 
    // We need to use the EndPosition to prevent the navigator from being moved past the
    // current element when processing only a subsection of the document.
    XPathNavigator EndPosition = rootNode.CreateNavigator();
    EndPosition.MoveToNext();
 
    while (rootNode.MoveToFollowing(XPathNodeType.Element, EndPosition))
    {
        // While there are following elements.
        if (nodesToIgnore.Contains(rootNode.LocalName))
        {
            // Nodes with a local name matching one provided in the 
            // nodesToIgnore list should be skipped.
            continue;
        }
 
        if (rootNode.HasAttributes)
        {
            // If the node has attributes move to the first one.
            rootNode.MoveToFirstAttribute();
 
            // Then loop through each one processing them.
            do
            {
                if (rootNode.Prefix == "xsi" ||
                      nodesToIgnore.Contains(rootNode.LocalName) ||
                      NodeIsNullOrEmpty(rootNode))
                {
                    // Nodes with a prefix "xsi" or a local name matching one provided in the 
                    // nodesToIgnore list should be skipped.
                    continue;
                }
 
                processNode(rootNode);
            }
            while (rootNode.MoveToNextAttribute());
 
            // Finally return to the parent node.
            rootNode.MoveToParent();
        }
 
        if (NodeIsGroup(rootNode) || NodeIsNullOrEmpty(rootNode))
        {
            return;
        }
 
        processNode(rootNode);
    }
}
 

 

From the top...

1. For nodes with attributes we run through each attibute and process them then return to the root.

2. For all other nodes we just process the node.

3. Groups do not have a text value so are not processed.

The clever bit is:

C#
MoveToFollowing(XPathNodeType.Element, EndPosition)

Which allows us to only iterate through elements (ignoring whitespace etc.) and prevents the navigator from moving past the last node when processing only a small section of the document.

Schema Awareness

And the last part of the puzzle! This is actually a very important part...

My encyption methods take a string and encrypt it into another string. This is great most of the time but will cause some nast issues for any fields which are not Text (string) fields. It is not very easy to encrypt non string data effectively so personally I wouldn't even bother. If you need to encrypt the data and have validation then I would suggest using validation in the code behind (which is pretty simple). I have written another library for doing just that which I will write about next.

In the meantime if you have non String fields you must use the "Schema Aware" overload of EncryptDocument() which takes a boolean value. This will then check the schema for the dataype of each node and ignore anything that is not a string. This prevents invalid data after encryption without the need to specify all those fields manually.

I'm particularly happy with this bit of code so here it is.

private static XmlSchemaSet GetSchemaSet(XmlFormHostItem form)
{
    XmlSchema Schema = XmlSchema.Read(form.Template.OpenFileFromPackage("mySchema.xsd"), null);
    XmlSchemaSet SchemaSet = new XmlSchemaSet();
    SchemaSet.Add(Schema);
    SchemaSet.Compile();
    return SchemaSet;
}
 
private static void SchemaAwareProcessNode(XmlSchemaSet schemaSet, XPathNavigator node, ProcessNodeDelegate processNode)
{
    XmlQualifiedName QualifiedName = new XmlQualifiedName(node.LocalName, node.NamespaceURI);
    XmlSchemaType Type = null;
 
    if (node.NodeType == XPathNodeType.Attribute)
    {
        XmlSchemaAttribute Attribute = (XmlSchemaAttribute)schemaSet.GlobalAttributes[QualifiedName];
        Type = Attribute.AttributeSchemaType;
    }
    else
    {
        XmlSchemaElement Element = (XmlSchemaElement)schemaSet.GlobalElements[QualifiedName];
        Type = Element.ElementSchemaType;
    }
 
    if (Type.TypeCode != XmlTypeCode.String)
    {
        return;
    }
    processNode(node);
}

The above code loads the schema of the form from the template and then discovers the type of node or attributes in the document and then chooses whether or not they should be processed.

Final Notes

1. I am not a security/cryptography expert! There may well be security issues here the least of which is that the key is currently hardcoded in the form code. If you find issues with it... I don't care :p this is not enterprise level security and was never intended to be such. It is much more than sufficient to prevent casual or even overly curious users from reading submitted form data.

2. The library is signed with a Strong-Name Key so you could do worse than use your own... It is also using the AllowPartiallyTustedCallers attribute which I would remove if you are in a Full Trust environment (and the form template is signed).

3. I included my other helpers in the source if you want to look, I will write articles about them later.

4. This library is targeted for .net 2.0 because I am stuck using Office 2007 which uses VSTA 1.0. As soon as I get an upgrade it will be onto bigger an better things, and I will be able to finally re-write this against a later .net framework version which will make it much better I think :p

5. If you have any problems with it let me know and I will update the code/article. Thanks for reading!

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)