Amazon SimpleDB for Windows Phone Part 4 -- TVMClient

Microsoft

Today I was made aware that in my series on Amazon SimpleDB for Windows Phone, I had mentioned that part 4 would contain some code samples and talk about encryption. I was confused for a second because I knew there was no part 4 and I thought the series was done. The series is not done. This is part 4 where we'll show how to decrypt the credentials sent from the TVM. This one goes out to reader @jayborseth.

One thing to note is that since I started the series, an AWS SDK for Windows Phone has been released in beta form. The example I'm about to show does not use this SDK (and I don't think the SDK has any work with the TVM).

As a refresher, the Token Vending Machine allows for an application to obtain temporary credentials for access to AWS services. In the anonymous way of obtaining credentials, there are 3 steps:

 

Step 1 - Register Device

Here is my code to register the device:

[code language="csharp"] public void RegisterDevice(string deviceId, string tvmKey, Action callback)
{ IDictionary parameters = new Dictionary();

parameters["uid"] = deviceId;
parameters["key"] = tvmKey;

string queryString = GetParametersAsString(parameters);

var uri = new Uri(string.Format("{0}/registerdevice?{1}", TvmUrl, queryString));

var hwr = WebRequest.Create(uri) as HttpWebRequest;
if (hwr != null)
{
    hwr.Method = "GET";
}

TemporaryTokenPackage tokenPackage = new TemporaryTokenPackage() { DeviceId = deviceId, Request = hwr };

AsyncCallback responseHandler = (async) =>
{
    TemporaryTokenPackage temporaryTokenPackage = (TemporaryTokenPackage)async.AsyncState;
    HttpWebRequest request = temporaryTokenPackage.Request;

    HttpWebResponse response = null;
    try
    {
        response = (HttpWebResponse)request.EndGetResponse(async);
        callback(true, null);

    }
    catch (WebException we)
    {
        if (((HttpWebResponse)we.Response).StatusDescription == "Conflict")
        {
            callback(true, null);
        }
        else
        {
            callback(false, we);
        }

    }
};

hwr.BeginGetResponse(responseHandler, tokenPackage);

} [/code]

Couple of things. Here is my TemporaryTokenPackage which I used to pass pieces of data around the async handlers:

[code language="csharp"] public class TemporaryTokenPackage
{ public HttpWebRequest Request { get; set; } public string DeviceId { get; set; } } [/code]

In the RegisterDevice method, we setup a typical request having a device ID and a tvm key. The tvm key is a Guid string without dashes. So,

tvmKey = Guid.NewGuid().ToString().Replace("-", "");

You set the key and tell the TVM what key you are going to use using this call. The TVM will eventually use that key to encrypt the credentials.

The /registerdevice HTTP call doesn't do much. If it returns, then you've successfully registered the device. If you get any error, you did not register the device and something is wrong. Try again.

Step 2 - Ask for Temporary Credentials

This is the part of the process where you ask the TVM for temporary credentials. If you already have a set of temporary credentials and they haven't expired, you don't need to ask for another set. Otherwise, get out your knee pads and beg. Here is how I did it:

[code language="csharp"] public void GetTemporaryCredentials(string deviceId, string tvmKey, Action callback)
{ string timeStamp = GetFormattedTimestampIso8601(0); string signature = Hmac2556Sign(timeStamp, tvmKey);

IDictionary parameters = new Dictionary();
parameters["uid"] = deviceId;
parameters["timestamp"] = timeStamp;
parameters["signature"] = signature;

string queryString = GetParametersAsString(parameters);
var uri = new Uri(string.Format("{0}/gettoken?{1}", TvmUrl, queryString));

var hwr = WebRequest.Create(uri) as HttpWebRequest;
AsyncCallback responseHandler = async =>
{
    var request = (HttpWebRequest)async.AsyncState;

    HttpWebResponse response = null;
    try
    {
        response = (HttpWebResponse) request.EndGetResponse(async);

    }
    catch (WebException we)
    {
        callback(null, we);
    }

    if (IsGoodResponse(response))
    {
        Stream stream = response.GetResponseStream();
        var sr = new StreamReader(stream);

        var responseText = sr.ReadToEnd();
        sr.Close();

        var dataToDecrypt = Convert.FromBase64String(responseText);
        var plainText = Decrypt(dataToDecrypt, tvmKey);

        var temporaryCredentials = AwsTemporaryCredentialFactory.Create(plainText);

        callback(temporaryCredentials, null);

        return;

    }
    else
    {
        string responseText = "noresponse";

        if (response != null)
        {
            responseText = response.StatusCode.ToString();
        }

        callback(null, new WebException("Bad web response, StatusCode=" + responseText));

    }
};

hwr.BeginGetResponse(responseHandler, hwr);

} [/code]

Step 3 - Decrypt Your New Creds

This is where, as they say on MTV Cribs, the magic happens. Note that at the start of this method, we're calculating the signature. This is what we talked about in Part 1. The bulk of this method is setting up the async HTTP call and dealing with the response. If we have a good HTTP response, we know that the TVM sent us something secret and we need to get it out. After encrypting the temporary credentials, the TVM will Base64 encode the result. So we need to decode that data using the .NET framework methods and we simply call Convert.FromBase64String which gives us a lovely byte array. Oh byte array.

This byte array is the raw encrypted credentials and we need to decrypt it. I pass into the Decrypt method the binary data to decrypt and the same tvm key that we used to register the device. Here's the Decrypt method:

[code language="csharp"] public string Decrypt(byte[] dataToDecrypt, string secretKey)
{ var secretKeyToUse = DecodeHex(secretKey.ToCharArray()); var cipher = CipherUtilities.GetCipher("AES/CBC/PKCS7Padding");

cipher.Init(false, new KeyParameter(secretKeyToUse));

int size =  cipher.GetOutputSize(dataToDecrypt.Length);
var results = new byte[size];

int olen = cipher.ProcessBytes(dataToDecrypt, 0, dataToDecrypt.Length, results, 0);
cipher.DoFinal(results, olen);

var result = Encoding.UTF8.GetString(results, 0, results.Length);

return result;

}

///<summary> /// Used to decode a plain text key into a key that can be used to decrypt. The data being passed in is assumed to be a /// series of hex digits and this converts those 2-digit hex bytes into a single byte array. /// </summary> /// This is adapated from the org.apache.commons.codec.binary.Hex java source code at http://kickjava.com/src/org/apache/commons/codec/binary/Hex.java.htm /// ///An array of characters containing hex digits /// A byte array containing the decoded hex data in binary format. public static byte[] DecodeHex(char[] data)
{ int len = data.Length;

if ((len &amp;amp; 0x01) != 0)
{
    throw new DataLengthException(&quot;Odd number of characters.&quot;);
}

var outresult = new byte[len &gt;&gt; 1];

// two characters form the hex value.
for (int i = 0, j = 0; j &amp;lt; len; i++)
{
    var f = Convert.ToInt32(data[j++].ToString(), 16) &amp;lt;&amp;lt; 4;
    f = f | Convert.ToInt32(data[j++].ToString(), 16);

    outresult[i] = (byte)(f &amp;amp; 0xFF);
}

return outresult;

} [/code]

Unfortunately, the key as we have it in memory is not what the TVM used to encrypt with. We need the DecodeHex method to rearrange the bits until we get a key that is the same format as was used to encrypt. Using this decoded key, we're using the Bouncy Castle CipherUtilities to do the actual decryption. Assuming this works, we'll end up with a UTF8 encoded string of decrypted data. But wait! There's more!

Just having this string doesn't give us everything. We need to split this packed string into a useable set of credentials. I used a factory to unpack this string into its constituent pieces of data (note that this is nearly identical to how the Andoid SDK sample did it):

[code language="csharp"] public class AwsTemporaryCredentialFactory
{ private const string AccessKeyName = "accessKey"; private const string SecretKeyName = "secretKey"; private const string SecurityTokenKeyName = "securityToken"; private const string ExpirationDateKeyName = "expirationDate";

///&lt;summary&gt; /// Used to create a set of temporary security credentials from the response provided by the

/// Token Vending Machine. /// </summary> ///The response from the Token Vending Machine /// A set of temporary AWS credentials public static AwsTemporaryCredentials Create(string credentialString) { AwsTemporaryCredentials credentials = new AwsTemporaryCredentials { AccessKey = ExtractElement(credentialString, AccessKeyName), SecretKey = ExtractElement(credentialString, SecretKeyName), SecurityToken = ExtractElement(credentialString, SecurityTokenKeyName), ExpirationDate = AwsTemporaryCredentials.GetExpirationTimeFromMilliseconds( ExtractElement(credentialString, ExpirationDateKeyName)) };

    return credentials;

}

///

<summary> /// Used to extract a piece of data from a json string. /// </summary> /// This is a C# port of the Java version written by Amazon.com ///The raw string to exctract the element from. ///the name of the piece of data to extract. /// The value of the exctracted element. private static String ExtractElement(String json, String element) { bool hasElement = (json.IndexOf(element) != -1); if (hasElement) { int elementIndex = json.IndexOf(element); int startIndex = json.IndexOf(""", elementIndex); int endIndex = json.IndexOf(""", startIndex + 1);

        return json.Substring(startIndex + 1, endIndex - (startIndex + 1));
    }

    return null;
}

} [/code]

This retrieves a AwsTemporaryCredentials object which I've defined as:

[code language="csharp"] public class AwsTemporaryCredentials
{ public string AccessKey { get; set; } public string SecretKey { get; set; } public string SecurityToken { get; set; } public DateTime ExpirationDate { get; set; }

///

&lt;summary&gt; /// Handles converting milliseconds since 01-01-1970 into a useable DateTime. /// &lt;/summary&gt; ///The number of milliseconds since 01-01-1970 as a string /// A DateTime that is 01-01-1970 plus the number of milliseconds public static DateTime GetExpirationTimeFromMilliseconds(string milliseconds) { long longMillseconds; Int64.TryParse(milliseconds, out longMillseconds);

    return GetExpirationTimeFromMilliseconds(longMillseconds);

}

///

&lt;summary&gt; /// Handles converting milliseconds since 01-01-1970 into a useable DateTime. /// &lt;/summary&gt; ///The number of milliseconds since 01-01-1970 /// A DateTime that is 01-01-1970 plus the number of milliseconds public static DateTime GetExpirationTimeFromMilliseconds(long milliseconds) { var ticks = milliseconds*TimeSpan.TicksPerMillisecond; var correctedDate = new DateTime(1970, 1, 1).Add(new TimeSpan(ticks)); return correctedDate; }

} [/code]

So there you go. 3 steps necessary to obtain temporary credentials from the Token Vending Machine (using the anonymous method). Once we have a set of temporary credentials, we can use those credentials just as we would with a "normal" set of credentials to utilize Amazon AWS services. Tired yet?

Questions? Comments? Confused? Drop a comment below if you need any help. Don't forget to follow me on Twitter @kenstone.