Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

SerializableSecureString

4.88/5 (12 votes)
19 Oct 2013CPOL5 min read 31K   518  
SerializableSecureString wraps System.SecureString so that it can participate as a DataMember while maintaining maximum data security.

Introduction

Handling sensitive data is de jour in modern programming and doing so in a consistent method should be part of any secure programming best practices. In many cases, the requirements of regulatory or industry standards bodies must also be met. For example, the data security standards of the credit card industry go so far as to define expectations on data-in-flight as well as data-at-rest, including data-at-rest transiently in memory. .NET has System.SecureString to help with the latter, and WCF provides both Transport and Message encryption to deal with the former. However, the two cannot be simply married in a DataContract because SecureString is deliberately non-serializable. This article describes a wrapper for SecureString that is able to participate as a DataMember using a preconfigured X.509 certificate to protect the data content while serialized, thus allowing DataContracts of the form:

C#
[DataContract]
public class SecureDataContract
{
    :
    [DataMember]
    public SerializableSecureString SecureContent { get; private set; }
}

The programmatic simplicity of this methodology was the chief motivator for this work. Other than binding the DataContract (via constructor injection) to the correct X.509 certificate, developers have no other cryptographic concerns with secure data storage or movement; they simply interact with a Type in a normal manner. By disentangling the data storage problem from the data consumption problem, the scope of secure data handling concerns is limited to areas in the code where the sensitive data is extracted to clear text for the implementation of business problems. The scope limitation facilitates the development of best practices, data handling guidelines, and perhaps even automated checking for possible exposures.

The Basics

SerializableSecureString wraps a private member variable, Content. The wrapper implements both IXmlSerializable, to hook the serialization process, and IDisposable, to housekeep Content, as is best practice for SecureString. A few helper methods allow consuming code access to the underlying Content or its properties.

C#
    [Serializable]
    public class SerializableSecureString : IXmlSerializable, IDisposable
    {
        :
        SecureString Content = new SecureString();
        :
        #region Content helpers
        /// <summary>
        ///     Clear and load initial data into the SecureString
        /// </summary>
        /// <param name="clearText"></param>
        public void Initialize(string clearText)
        {
            Content.Clear();
            Append(clearText);
        }

        /// <summary>
        ///     Append to current SecureString content
        ///     Throws ReadOnlyContentException if SecureString has been locked
        /// </summary>
        /// <param name="clearText">text to append</param>
        public void Append(string clearText)
        {
            if (clearText != null)
            {
                if (Content.IsReadOnly()) throw new ReadOnlyContentException();
                // iterate the loop to avoid making a copy of the
                // cleartext accidentally
                foreach (char t in clearText)
                {
                    Content.AppendChar(t);
                }
            }
        }

        /// <summary>
        ///     Retrieve the data currently within the SecureString
        ///     Caller has the responsibility for keeping this data hopefully
        ///     in a GC0 collection usage.
        /// </summary>
        /// <returns>ClearText from SecureString</returns>
        public string Extract()
        {
            IntPtr bstr = Marshal.SecureStringToBSTR(Content);
            string copiedText = Marshal.PtrToStringAuto(bstr);
            Marshal.ZeroFreeBSTR(bstr);
            return copiedText;
        }

        /// <summary>
        ///     Seal the SecureString from further modification
        /// </summary>
        public void MakeReadOnly()
        {
            Content.MakeReadOnly();
        }

        /// <summary>
        ///     Check read only state of SecureString
        /// </summary>
        /// <returns>false if data can be added</returns>
        public bool IsReadOnly()
        {
            return Content.IsReadOnly();
        }

        /// <summary>
        ///     Return a copy of the internal SecureString
        /// </summary>
        /// <returns></returns>
        public SecureString CloneData()
        {
            return Content.Copy();
        }
        #endregion
         :
}

Two other helpers provide access to the configured certificate store. These are useful in implementing DataContract constructors that initialize the SerializableSecureStrings as shown later:

C#
[Serializable]
public class SerializableSecureString : IXmlSerializable, IDisposable
{
    :
    public IEnumerable<X509Certificate2> Find(string thumbPrint)
    {
        return Find(thumbPrint, CertificateStore);
    }

    public static IEnumerable<X509Certificate2> Find(string thumbPrint, X509Store store)
    {
        store.Open(OpenFlags.ReadOnly);
        IEnumerable<X509Certificate2> results =
            store.Certificates.Find(X509FindType.FindByThumbprint, thumbPrint, false)
                 .Cast<X509Certificate2>();
        store.Close();
        return results;
    }
     :
}

IXmlSerializable

Implementation of IXmlSerializable begins by defining the representational post-serialization schema.

XML
<SecureStringEnvelope>
    <EncryptionContext>
        <ClearText />
        <IsReadOnly>false</IsReadOnly>
        <CertificateContext>
            <ThumbPrint />
            <Store />
            <Location />
        </CertificateContext>
    </EncryptionContext>
</SecureStringEnvelope>"

The relevant inner elements of this schema are:

  • ClearText, the value of Content at the time of serialization
  • IsReadOnly, the read only state of Content at time of serialization
  • ThumbPrint, the thumbprint of an X.509 certificate used to encrypt ClearText during serialization
  • Store, the name of the certificate store in which the certificate specified by ThumbPrint is installed
  • Location, the location of the Store, either CurrentUser or LocalMachine

These schema elements have related or backing properties in the wrapper which play vital roles during serialization.

C#
    public class SerializableSecureString : IXmlSerializable, IDisposable
    {
        :
        // Design note: The ge">    public class SerializableSecureString : IXmlSerializable, IDisposable
    {
        :
        #region IXmlSerializable
        public XmlSchema GetSchema()
        {
            return null;
        }

        public void ReadXml(XmlReader reader)
        {
            var doc = new XmlDocument();
            // position reader to the start of our serialization stream
            // because at entry we are in the DataContractSerializer's envelope
            reader.MoveToContent();

           while (reader.Read())
           {
                if (reader.NodeType == XmlNodeType.Element
                    && reader.LocalName == _Envelope)
                {
                    break;
                }
            }

            doc.LoadXml(reader.ReadOuterXml());
            Value = doc;
        }

        public void WriteXml(XmlWriter writer)
        {
            Value.Save(writer);
        }

        #endregion
    }
}

XML Encryption

The W3C's recommendation on encrypting XML describes a process for the encryption of one or more elements in an XML document.  The basic procedure replaces a fragment of the document with an encrypted equivalent on output and reverses the process on input. In .NET, this standard is implemented by the EncryptedXml class (System.Security.Cryptography.Xml). While a variety of cryptographic mechanisms are supported, the most straight forward use of EncryptedXml is provided when an X.509 certificate is used to specify the encryption processes. The wrapper's private Value property accessors leverage EncryptedXml to protect the ClearText once it is extracted from the SecureString.

C#
/// <summary>
///     Moves Content into and out of serialization streams
/// </summary>
private XmlDocument Value
{
    // Move the SecureString into the serialization in encrypted xml
    get
    {
        var doc = new XmlDocument();
        doc.LoadXml(SerializationXsi);
        // copy Content into document
        //A priori knowledge of location of ClearText element
        XmlNode elmt = doc.DocumentElement.FirstChild.FirstChild;
        elmt.InnerText = Extract();
        var xmlEnc = new EncryptedXml(doc);
        EncryptedData encrData = xmlEnc.Encrypt(elmt as XmlElement, Certificate);
        EncryptedXml.ReplaceElement(elmt as XmlElement, encrData, false);
        LoadCertificateContext(doc);
        return doc;
    }
    set
    {
        // Decrypt the xml and reload the SecureString
        XmlDocument doc = value;
        bool bReadOnly = UnloadCertificateContext(doc);
        // Decrypt the document
        var encrXml = new EncryptedXml(doc);
        encrXml.DecryptDocument();
        // load the secure string
        Initialize(doc.DocumentElement.FirstChild.FirstChild.InnerText);
        if (bReadOnly) Content.MakeReadOnly();
    }
}

The get accessor loads an instance of the serialization schema with the current ClearText value of Content, invokes EncryptedXml to encrypt the ClearText node, then populates the remainder of the schema from current member variables. The set accessor reverses the process, starting with deserializing the current member variables, then decrypting ClearText and reloading Content. Content’s IsReadOnly() state is preserved. Note that the set accessor throws a CertificateException if the certificate specified in the incoming serialization instance is not installed in the specified location and certificate store. The current implementation demands that both sender and receiver use the same certificate, and that both sender and receiver have access to the certificate’s private key.  A before and after picture of the encryption effects are shown below:

BeforeAfter
Image 1Image 2

Herein lies the problem with any field based encryption strategy: payload bloat, especially with X.509 certificates. The bloat is, however, offset by several factors that make the solution viable:

  • The data is highly compressible on the wire.
  • The programmatic implementation is relatively simple due in part to the deep support for certificates within the base class library.
  • DataContract syntax is familiar and straight forward when working with the certificates
  • Experience indicates that there are few (one to two) of encrypted fields per DataContract
  • There is significant administrative ease in using X.509 certificates to convey cryptographic details in field deployments.

DataContract Recommendations

When implementing a DataContract for a class which contains SerializableSecureString, the following steps are recommended:

  • Provide a private default constructor. A default constructor is required by the serializer, but does not have to be public.
  • Make the set accessor of the SerializableSecureString properties private.
  • Implement one or more parameterized constructors that create the SerializableSecureString members with the appropriate certificate.

The sample below illustrates these points.

C#
    [DataContract]
    public class SecureDataContract
    {
        private SecureDataContract() // required by serializer
        {
        }

        public SecureDataContract(X509Certificate2 cert, X509Store store)
        {
            SecureContent = new SerializableSecureString(cert, store);
        }

        public SecureDataContract(SerializableSecureString secureString)
        {
            SecureContent = secureString;
        }

        public SecureDataContract(string thumbPrint, X509Store store)
        {
            X509Certificate2 cert = SerializableSecureString.Find(
                thumbPrint, store).FirstOrDefault();
            SecureContent = new SerializableSecureString(cert, store);
        }

        [DataMember]

        public SerializableSecureString SecureContent { get; private set; }
    }
}

Points Of Interest

Working with X.509 certificates in unit test environments is a best trying, especially when private keys are involved as here. To avoid the tedium, a certificate generator was developed to provide dynamically created X.509 certificates. The generator leverages the BouncyCastle crypto library to produce the inputs to create an X509Certificate2 instances for use in the unit tests. X509Certificate2 provides a convenient API for working with the public and private keys of a certificate, including persisting or destroying the private key’s backing store in the Cryptographic Service Provider. The generator is packaged as a separate assembly, X509Generators.

C#
 namespace X509Generators
 {
     :
     public static X509Certificate2 GenerateCertificate(StoreLocation location,
                                                        string subjectName,
                                                        SecureString password,
                                                        ExtendedKeyUsageEnum purpose =
                                                            ExtendedKeyUsageEnum.EmailProtection)
     {
         // Create a BouncyCastle keypair and X509Certificate holding the public key
         GeneratedCertificate bouncyCert = GenerateCertificate(subjectName, purpose);
         // Create an exportable X509Certificate2 to return from the BouncyCastle cert
         var cert = new X509Certificate2(bouncyCert.Certificate.GetEncoded(), password,
                                         X509KeyStorageFlags.Exportable);
        :
        return cert;
     }
     :
}

In the DEBUG configuration, SerializedSecureString exposes a testing constructor that initializes itself from a dynamically generated certificate. The _autogenerated flags the use of the dynamic generator.                                       

C#
    public class SerializableSecureString : IXmlSerializable, IDisposable
    {
    :
 :#if DEBUG
        /// <summary>
        ///     Testing constructor
        ///     Automatically generates and installs a new certificate for use in a
        ///     test run. Certificate is uninstalled and private key deleted on Dispose().
        /// </summary>
        /// <param name="purpose"></param>
        public SerializableSecureString(
            ExtendedKeyUsageEnum purpose = ExtendedKeyUsageEnum.EmailProtection)
        {
            CertificateStore = new X509Store(StoreName.My, StoreLocation.CurrentUser);
            Content = new SecureString();
            Certificate = X509Generation.GenerateCertificate(CertificateStore.Location, GetType().Name,
                                            new SecureString());
            _autoGenerated = true;
            ThumbPrint = Certificate.Thumbprint;
            SetCertificateInStore(Certificate);
        }#endif
        :
    }
#endif

The _autogenerated flag is used in the IDisposable implementation to detect a dynamic certificate and delete the backing private key when the certificate is Disposed.

C#
public SerializableSecureString(SerializableSecureString instance, bool bCopyData = false)
    {
        :
    protected virtual void Dispose(bool isDisposing)
    {
        if (!_disposed)
        {
            if (isDisposing)
            {
                Content.Dispose(); // kill the protected data now
                if (Certificate != null && _autoGenerated)
                {                        // house keep the autogenerated certificates from disk
                    // _autogenerated flag can be true only in DEBUG build
                    if (Certificate.HasPrivateKey)
                    {
                        // delete the private key
                        var pvKey = Certificate.PrivateKey as RSACryptoServiceProvider;
                        pvKey.PersistKeyInCsp = false;
                        pvKey.Clear();
                    }
                    CertificateStore.Open(OpenFlags.ReadWrite);
                    CertificateStore.Remove(Certificate);
                    CertificateStore.Close();
                }
            }
            _disposed = true;
        }
    }
    :
}

A build condition in the csproj causes the necessary reference to the X509Generators project to be made. This has been hand edited into the csproj as shown:

XML
<Choose>
  <When Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
    <ItemGroup>
      <ProjectReference Include="X509Generators\X509Generators.csproj">
        <Project>{483bd6f8-b1f8-402d-aca4-9ea13a173baf}</Project>
        <Name>X509Generators</Name>
      </ProjectReference>
    </ItemGroup>
  </When>
</Choose>">

Thus the X509Generator assembly is not required for the Release build.

Using the Code

The code is delivered as a single, VS2012 solution with three projects containing:

  • SerializableSecureString, the implementation
  • X509Generators, the dynamic certificate generators
  • SerialzableSecureStringTests, xUnit tests to exercise code

Code can be exercised by running the unit tests following a successful build.

History

  • 19th October, 2013: Initial version

License

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