Using Oauth tickets across several services?

After talking with Brock Allen in the comments to the original post, I can’t really guarantee this is a good/safe solution, but this is the code I ended up using. (Note: a version of this code is available as a nuget package.)

I created a IDataProtector implementation that uses AES:

internal class AesDataProtectorProvider : IDataProtector
{
    // Fields
    private byte[] key;

    // Constructors
    public AesDataProtectorProvider(string key)
    {
        using (var sha1 = new SHA256Managed())
        {
            this.key = sha1.ComputeHash(Encoding.UTF8.GetBytes(key));
        }
    }

    // IDataProtector Methods
    public byte[] Protect(byte[] data)
    {
        byte[] dataHash;
        using (var sha = new SHA256Managed())
        {
            dataHash = sha.ComputeHash(data);
        }

        using (AesManaged aesAlg = new AesManaged())
        {
            aesAlg.Key = this.key;
            aesAlg.GenerateIV();

            using (var encryptor = aesAlg.CreateEncryptor(aesAlg.Key, aesAlg.IV))
            using (var msEncrypt = new MemoryStream())
            {
                msEncrypt.Write(aesAlg.IV, 0, 16);

                using (var csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
                using (var bwEncrypt = new BinaryWriter(csEncrypt))
                {
                    bwEncrypt.Write(dataHash);
                    bwEncrypt.Write(data.Length);
                    bwEncrypt.Write(data);
                }
                var protectedData = msEncrypt.ToArray();
                return protectedData;
            }
        }
    }

    public byte[] Unprotect(byte[] protectedData)
    {
        using (AesManaged aesAlg = new AesManaged())
        {
            aesAlg.Key = this.key;

            using (var msDecrypt = new MemoryStream(protectedData))
            {
                byte[] iv = new byte[16];
                msDecrypt.Read(iv, 0, 16);

                aesAlg.IV = iv;

                using (var decryptor = aesAlg.CreateDecryptor(aesAlg.Key, aesAlg.IV))
                using (var csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
                using (var brDecrypt = new BinaryReader(csDecrypt))
                {
                    var signature = brDecrypt.ReadBytes(32);
                    var len = brDecrypt.ReadInt32();
                    var data = brDecrypt.ReadBytes(len);

                    byte[] dataHash;
                    using (var sha = new SHA256Managed())
                    {
                        dataHash = sha.ComputeHash(data);
                    }

                    if (!dataHash.SequenceEqual(signature))
                        throw new SecurityException("Signature does not match the computed hash");

                    return data;
                }
            }
        }
    }
}

And then used this in an ISecureDataFormat implementation like so:

public class SecureTokenFormatter : ISecureDataFormat<AuthenticationTicket>
{
    // Fields
    private TicketSerializer serializer;
    private IDataProtector protector;
    private ITextEncoder encoder;

    // Constructors
    public SecureTokenFormatter(string key)
    {
        this.serializer = new TicketSerializer();
        this.protector = new AesDataProtectorProvider(key);
        this.encoder = TextEncodings.Base64Url;
    }

    // ISecureDataFormat<AuthenticationTicket> Members
    public string Protect(AuthenticationTicket ticket)
    {
        var ticketData = this.serializer.Serialize(ticket);
        var protectedData = this.protector.Protect(ticketData);
        var protectedString = this.encoder.Encode(protectedData);
        return protectedString;
    }

    public AuthenticationTicket Unprotect(string text)
    {
        var protectedData = this.encoder.Decode(text);
        var ticketData = this.protector.Unprotect(protectedData);
        var ticket = this.serializer.Deserialize(ticketData);
        return ticket;
    }
}

The ‘key’ parameter on the constructor can then set to the same value on a number of services and they will all be able to decrypt (‘unprotect’) and use the ticket.

Leave a Comment