Securing Azure Service Bus and Azure Queuing Messages in .NET

Securing data at rest is often the center of concern for cloud adoption in the enterprise.  Secure communication protocols like SSL partially address the issues.  However, SSL falls short once data is decrypted and persisted to a data store.   While .NET includes encryption classes there are no instructions for bringing encryption to bear on, for example, Azure Messaging services like Service Bus and Azure Queuing.

One simple path is to encrypt individual fields in the message.  As the number of messages increases, however, rewriting code to encrypt individual fields becomes impractical.  Another approach is to encrypt a message’s payload.  A Pipeline and Envelope architecture handling the encryption is a suitable implementation approach that leads to more reusable code across all Azure Messaging infrastructures.   The Envelope separates the message format from the platform and the Pipeline separates encryption from class Serialization.   The remaining paragraphs explain how to achieve this.

Designing a Secure Envelope

An Envelope is a message within a message.  In this sample; the outer part is an encrypted byte array and the inner part contains the data.  The following code depicts the encrypted data class and Envelope class designs.

[DataContract(Namespace = "http://schemas.datacontract.org/2004/07/Message.Services", Name = "EncryptedData")]
public sealed class ByteDataContract
{
    [DataMember]
    public byte[] Bytes { get; set; }
}
 [DataContract(Namespace = "http://schemas.datacontract.org/2004/07/Message.Services", Name = "MessageEnvelope")]
public sealed class MessageEnvelope
{
    #region Properties
    [DataMember]
    public DateTime CreateDateTime { get; set; }
    [DataMember]
    public string InternalVersion { get; set; }
    [DataMember]
    public string MessageType { get; set; }
    [DataMember]
    public string Payload { get; set; }
 
    #endregion
 
    #region Initialization and Construction
 
    private void InitProperties()
    {
        this.Payload = null;
        this.CreateDateTime = DateTime.Now;
        var assm = Assembly.GetExecutingAssembly().GetName();
        this.InternalVersion = assm.FullName;
 
        this.MessageType = "";
    }
 
    public MessageEnvelope(string payload)
    {
        this.InitProperties();
        this.Payload = payload;
    }
 
    #endregion
}
 

ByteDataContract will contain the encrypted version of a more specialized message class.  ByteDataContract will, in turn, will become the Payload property on the EnvelopeMessage.  EnvelopeMessage will then be handled by whatever Azure Messaging Infrastructure a developer decides to employ. 

A Pipeline Architecture that operates on the MessageEnvelope class works on the MessageEnvelope layers.  First decrypting/encrypting the message and then unpacking/packing the specialized message class.  All layers utilize a set of Serialization and Deserialization utility methods.  Utility Serialization and Deserialization methods are captured in the following code.

public static class PayloadTransformServices
{
public static T DeSerializeToClass<T>(string xmlData, List<Type> internalTypes)
{
    T obj;
    DataContractSerializer serializer = new DataContractSerializer(internalTypes[0], internalTypes);
    var reader = new XmlTextReader(new StringReader(xmlData));
    obj = (T)serializer.ReadObject(reader);
    return obj;
}
 
public static StringBuilder SerializeToStringBuilder(object obj, List<Type> internalTypes)
{
    var sb = new StringBuilder();
    var xmlWrite = new XmlTextWriter(new StringWriter(sb));
    var mainTypeIndex = 0;
 
    for (int n = 0; n < internalTypes.Count; ++n)
    {
        if (obj.GetType() == internalTypes[n])
        {
            mainTypeIndex = n;
        }
    }
 
    DataContractSerializer serializer = new DataContractSerializer(internalTypes[mainTypeIndex], internalTypes);
    serializer.WriteObject(xmlWrite, obj);
    return sb;
}
}
 

The internalTypes parameter is required; because a class may be composed of other internal classes.  Serialization only works on primitives unless the serializer is directed to use other classes.  The first type in the internalTypes list is assumed to be the class being Deserialized.  A complete review of the Serialization involved in the process is beyond the scope of this article; however you’ll find a good review in the resources at the end of the article. 

When a developer is working with WCF and Serialization; for security reasons, WCF Messages have buffer limitations.  There are default settings on the message and on the message contents.  For example: the Payload property could easily exceed the default message size for message property contents.  Developers may need to adjust code like in the following sample if message sizes exceed these defaults.

int KB = 1024;
Binding binding = null;
int maxMessageSize = 256 * KB;
var testBinding = new BasicHttpBinding();
 
testBinding.MaxBufferSize = maxMessageSize;
testBinding.MaxReceivedMessageSize = testBinding.MaxBufferSize;
testBinding.ReaderQuotas.MaxStringContentLength = testBinding.MaxBufferSize;

As stated earlier a Pipeline Architecture operates on the Envelope.

Pipeline Architecture

Pipelines are a good way to implement processes with stages where each stage gets input from the prior state and generates input for the next stage.  The underlying infrastructure is only aware of the MessageEnvelope class.  Core Pipeline operations are handled in extension methods on the MessageEnvelope.  Separating the operations into Extension Methods separates the Message contract from the methods operating on the contract.  This ensures that code only needing the contract will only see the contract and nothing more.  So, for example, the Messaging Infrastructure code is only aware of the MessageEnvelope class and not aware of the Extensions that manipulate the class.  Extension methods on the MessageEnvelope follow.

public static void EncodePayload(this MessageEnvelope envelope, object rawPayload,List<IPayloadTransformation> encodeTrans)
{
    object obj = null;           
    bool firstOne = true;
 
    foreach (var trans in encodeTrans)
    {
        if (firstOne)
        {
            obj = trans.RunTransformation(rawPayload);
            firstOne = false;
        }
        else
        {
            obj = trans.RunTransformation(obj);
        }
    }
 
    envelope.Payload = obj.ToString();
}
 
public static T DecodePayload<T>(this MessageEnvelope envelope, List<IPayloadTransformation> decodeTrans)
{
    object obj = null;
    bool firstOne = true;
 
    foreach (var trans in decodeTrans)
    {
        if (firstOne)
        {
            obj = trans.RunTransformation(envelope.Payload);
            firstOne = false;
        }
        else
        {
            obj = trans.RunTransformation(obj);
        }
    }
 
    return (T)obj;
}
 

Methods utilize an IPayloadTransformation interface.  The interface is demonstrated in the following code.

public interface IPayloadTransformation
{
    object RunTransformation(object input);
}
 

Specialized IPayloadTransformation implementations handle serialization or encryption.  An implementation handling encryption follows.

internal enum TransformDirection
{
    None = 0,
    ResultToRaw = 1,
    RawToResult = 2
}
 
 
internal sealed class EncryptionTransform : IPayloadTransformation
{
    private TransformDirection _direction = TransformDirection.None;
 
    public EncryptionTransform(TransformDirection direction)
    { _direction = direction; }
 
    #region IPayloadTransformation Members
 
    public object RunTransformation(object input)
    {
        object obj = null;
 
        switch (_direction)
        {
            case TransformDirection.RawToResult:
                var byteData = new ByteDataContract();
                byteData.Bytes = PayloadTransformServices.Encrypt(input.ToString());
                obj = byteData;
                break;
            case TransformDirection.ResultToRaw:
                var byteDataClass = input as ByteDataContract;
                obj = PayloadTransformServices.Decrypt(byteDataClass.Bytes);
                break;
        }
 
        return obj;
    }
 
    public string OperationId
    {
        get { return "Encrypt"; }
    }
 
    #endregion
}
 

TransformationDirection directs the code to either Encrypt, turning the class into bytes, or Decrypt, turning the bytes into a class.  The implementation assumes another class has either serialized the string to XML data or will deserialize the string from XML data to a class.  Notice how the transformation operates on or creates a ByteDataContract.

Encrypt and Decrypt implementation details first require an Encryption primer.

.NET Encryption Primer

Cryptography has been a part of .NET since .NET was first released.  What follows is a short .NET cryptography introduction focusing on the classes used in the sample solution.  The Encrypt, Decrypt, and related methods used in the previous sample code follow.

static PayloadTransformServices()
{
    //These should either go in a config or here. We're really just trying to secure data in the cloud.
    _Key = SHA256.Create().ComputeHash( UnicodeEncoding.ASCII.GetBytes("hashed 256"));
    _IV =  MD5.Create().ComputeHash(UnicodeEncoding.ASCII.GetBytes("MD5 hash here"));
}
 
 
public static byte[] Encrypt(string xmlData)
{
    var streamDest = new MemoryStream();
    var streamSource = new MemoryStream(UTF8Encoding.UTF8.GetBytes(xmlData));
    var cryptoAlgorithm = new AesManaged();
           
    var cryptWrite = new CryptoStream(streamDest,
    cryptoAlgorithm.CreateEncryptor(_Key, _IV),
    CryptoStreamMode.Write);
 
    CopyFromToStream(streamSource, cryptWrite);
    cryptWrite.Flush();
    cryptWrite.Close();
    streamDest.Close();
 
    return streamDest.ToArray();
}
 
public static string Decrypt(byte[] encryptedData)
{
    var streamDest = new MemoryStream();
    var streamSource = new MemoryStream(encryptedData);
    var cryptoAlgorithm = new AesManaged();
    var strDest = "";
 
    var cryptRead = new CryptoStream(streamSource,
    cryptoAlgorithm.CreateDecryptor(_Key, _IV),
    CryptoStreamMode.Read);
 
    CopyFromToStream(cryptRead, streamDest);
    streamDest.Close();
 
    strDest = UTF8Encoding.UTF8.GetString(streamDest.GetBuffer());
 
    return strDest;
}
 
public static void CopyFromToStream(Stream from, Stream to)
{
    Byte[] bytes = new Byte[2048];
    Array.Clear(bytes, 0, 2048); // Clear the array.
 
    int dataRead = 0;
    do
    {
        dataRead = from.Read(bytes, 0, 2048);
        if (dataRead > 0) // We have data.
        {
            to.Write(bytes, 0, dataRead);
            Array.Clear(bytes, 0, 2048); // Clear the array.
        }
    } while (dataRead > 0);
 
}
 

Developers familiar with the .NET Stream class and its variants will notice similarities here.  Streams are often chained together in a Pipeline where one Stream feeds another Stream.  CryptoStream utilizes the same arrangement.  A Byte array is managed by a MemoryStream.  The MemoryStream handles reading and writing Bytes fed to or pulled from the CryptoStream class.  CopyFormToStream method mediates between the two Streams.

The CryptoStream requires two values the Key and the Initialization Vector.  The values “prime” the underlying algorithm and ensure, for example, that identical text blocks are not identical after each block is encrypted.  Because the sample works with XML data the bytes are assumed to be UTF8 encoded.  A more complete introduction to encryption can be found in resources at the end of the article.

All components discussed so far are packaged together in the Pipeline Factory.

Pulling it Together in a Pipeline Factory

Everything is packaged into a Pipeline and Pipeline Factory interface implementation.  The interfaces are demonstrated in the following code.

public interface IMessageEnvelopePipeline
{
    MessageEnvelope GetResponse();
}
 
public interface IMessageEnvelopePipelineFactory
{
    IMessageEnvelopePipeline Create(Uri originatingEndpoint,MessageEnvelope envelope);
}

The following code demonstrates how a Pipeline would be implemented for receiving a message.  The message pattern assumes an HTTP Request/Reply; sending a response MessageEnvelope.

public sealed class DataTransferReceivePipeline : IMessageEnvelopePipeline
{
    private MessageEnvelope _envelope = null;
 
    internal DataTransferReceivePipeline(MessageEnvelope envelope)
    {
        _envelope = envelope;
    }
 
 
    public MessageEnvelope GetResponse()
    {
        MessageEnvelope msg = new MessageEnvelope("");
        msg.MessageType = "Success";
 
        ClassHere data = “”;
 
        try
        {
            //Decrypt
            //Serialize to ClassHere class
            data = _envelope.DecodePayload<ClassHere>(TransformStacks.InBound);
      
              msg.PayLoad = “Got it”;
 
        }
        catch (Exception ex)
        {
            msg = new MessageEnvelope(ex);
 
        }
 
        return msg;
    }
 
}
 

A PipelineFactory handles building the Pipeline.  The Factory approach hides complex class creation details.  A Factory implementation example follows.

public class DataTransferPipelineFactory : IMessageEnvelopePipelineFactory
{      
    public IMessageEnvelopePipeline Create(Uri originatingEndpoint,MessageEnvelope envelope)
    {
        IMessageEnvelopePipeline pipeline = null;
 
        if (envelope.MessageType == "SomeClassHere")
        { pipeline = new DataTransferReceivePipeline(envelope); }
 
        return pipeline;
    }
}
 

The parts will work as follows:

  • The selected Messaging Infrastructure Deserializes the incoming message into a MessageEnvelope class.
  • An IMessageEnvelopePiplineFactory class is created and the MessageEnvelope is passed to the Create method.
  • GetResponse is invoked and the response is sent back through the Messaging Infrastructure.

MessageEnvelope Response contents can be as simple or as complicated as a developer makes them.  For example, a developer may find that the response should also be encrypted.  The example can be modified so the Response is an integer indicating whether the message was successfully consumed.

Conclusion

Securing data at rest is often the center of concern when an enterprise is considering adopting Azure Service Bus or Azure Queuing.  Encryption is often the answer to addressing data at rest.  Taking a Pipeline and Envelope Pattern approach allows reusability across both infrastructures and decouples the messaging infrastructure from the message handling.

Resources

Serialization and Queuing

Cryptography in .NET

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read