From 00a12681770db2ff4af817e31d20dd6e9b78f143 Mon Sep 17 00:00:00 2001 From: David Iffland Date: Sat, 12 May 2012 20:32:55 -0500 Subject: [PATCH] Include is raw code to implement a .NET TVMClient. This is include per discussion with Wade Matveyenko. This code does not compile and as such the project file has not been updated. This code was originally written to support a Windows Phone based TVMClient and has a dependency on BouncyCastle.Crypto.WP7. Additionally, it uses Action Delegates with multiple type parameters which wasn't introduced until .NET 3.5 and the current AWSSDK targets 2.0. Where available, code calls into the AWSSDKUtil for helpers. --- .../AwsTemporaryCredentialFactory.cs | 70 +++++ Amazon.TVMClient/AwsTemporaryCredentials.cs | 52 ++++ Amazon.TVMClient/TVMClient.cs | 241 ++++++++++++++++++ Amazon.TVMClient/TemporaryTokenPackage.cs | 23 ++ 4 files changed, 386 insertions(+) create mode 100644 Amazon.TVMClient/AwsTemporaryCredentialFactory.cs create mode 100644 Amazon.TVMClient/AwsTemporaryCredentials.cs create mode 100644 Amazon.TVMClient/TVMClient.cs create mode 100644 Amazon.TVMClient/TemporaryTokenPackage.cs diff --git a/Amazon.TVMClient/AwsTemporaryCredentialFactory.cs b/Amazon.TVMClient/AwsTemporaryCredentialFactory.cs new file mode 100644 index 000000000000..10dcb2b4fc1a --- /dev/null +++ b/Amazon.TVMClient/AwsTemporaryCredentialFactory.cs @@ -0,0 +1,70 @@ +/* + * Author: David Iffland, Heavy Code LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + */ + +using System; + +namespace AWSSDK.Amazon.TVMClient +{ + public class AwsTemporaryCredentialFactory + { + private const string AccessKeyName = "accessKey"; + private const string SecretKeyName = "secretKey"; + private const string SecurityTokenKeyName = "securityToken"; + private const string ExpirationDateKeyName = "expirationDate"; + + + + /// + /// Used to create a set of temporary security credentials from the response provided by the + /// Token Vending Machine. + /// + /// 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; + + } + + /// + /// Used to extract a piece of data from a json string. + /// + /// 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; + } + } +} diff --git a/Amazon.TVMClient/AwsTemporaryCredentials.cs b/Amazon.TVMClient/AwsTemporaryCredentials.cs new file mode 100644 index 000000000000..04cbc3fe454d --- /dev/null +++ b/Amazon.TVMClient/AwsTemporaryCredentials.cs @@ -0,0 +1,52 @@ +/* + * Author: David Iffland, Heavy Code LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + */ + +using System; + +namespace AWSSDK.Amazon.TVMClient +{ + /// + /// Used to contain a set of temporary credentials for Amazon AWS. + /// + public class AwsTemporaryCredentials + { + public string AccessKey { get; set; } + public string SecretKey { get; set; } + public string SecurityToken { get; set; } + public DateTime ExpirationDate { get; set; } + + /// + /// Handles converting milliseconds since 01-01-1970 into a useable DateTime. + /// + /// 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); + + } + + /// + /// Handles converting milliseconds since 01-01-1970 into a useable DateTime. + /// + /// 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; + } + + } +} diff --git a/Amazon.TVMClient/TVMClient.cs b/Amazon.TVMClient/TVMClient.cs new file mode 100644 index 000000000000..ad8d3b9cfc8c --- /dev/null +++ b/Amazon.TVMClient/TVMClient.cs @@ -0,0 +1,241 @@ +/* + * Author: David Iffland, Heavy Code LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + */ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Security.Cryptography; +using System.Text; +using Amazon.Util; + +namespace AWSSDK.Amazon.TVMClient +{ + public class TVMClient + { + public string TvmUrl { get; set; } + + + /// + /// Called to initially register a device with the TVM. + /// + /// A 32 character device id. + /// A 32 character user defined key + /// The callback method + public void RegisterDevice(string deviceId, string tvmKey, Action callback) + { + IDictionary parameters = new Dictionary(); + + parameters["uid"] = deviceId; + parameters["key"] = tvmKey; + + string queryString = AWSSDKUtils.GetParametersAsString(parameters); + + var uri = new Uri(string.Format("{0}/registerdevice?{1}", TvmUrl, queryString)); + + var hwr = WebRequest.Create(uri) as HttpWebRequest; + if (hwr == null) + { + throw new NullReferenceException("Could not create HttpWebRequest"); + } + + hwr.Method = "GET"; + + var 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); + } + + /// + /// Called to retrieve a set of temporary credentials from the TVM. + /// + /// The same 32 character device ID used in RegisterDevice + /// The same 32 character TVM key used in RegisterDevice + /// The callback method + public void GetTemporaryCredentials(string deviceId, string tvmKey, Action callback) + { + + string timeStamp = AWSSDKUtils.GetFormattedTimestampISO8601(0); + string signature = AWSSDKUtils.HMACSign(timeStamp, tvmKey, new HMACSHA256()); + + IDictionary parameters = new Dictionary(); + parameters["uid"] = deviceId; + parameters["timestamp"] = timeStamp; + parameters["signature"] = signature; + + + string queryString = AWSSDKUtils.GetParametersAsString(parameters); + var uri = new Uri(string.Format("{0}/gettoken?{1}", TvmUrl, queryString)); + + + var hwr = WebRequest.Create(uri) as HttpWebRequest; + if (hwr == null) + { + throw new NullReferenceException("Could not create 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(); + if (stream != null) + { + 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); + } + else + { + callback(null, new WebException("Response stream was 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); + } + + + + /// + /// Override this to take a closer look at the response, for example to look at the status code. + /// + /// Default implementation looks for HttpResponse.StatusCode == 200. + /// + /// + /// + protected virtual bool IsGoodResponse(HttpWebResponse response) + { + if (response == null) + return false; + + return response.StatusCode == HttpStatusCode.OK; + } + + 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; + } + + /// + /// 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. + /// + /// 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 & 0x01) != 0) + { + throw new DataLengthException("Odd number of characters."); + } + + var outresult = new byte[len >> 1]; + + // two characters form the hex value. + for (int i = 0, j = 0; j < len; i++) + { + var f = Convert.ToInt32(data[j++].ToString(), 16) << 4; + f = f | Convert.ToInt32(data[j++].ToString(), 16); + + outresult[i] = (byte)(f & 0xFF); + } + + return outresult; + } + + } +} diff --git a/Amazon.TVMClient/TemporaryTokenPackage.cs b/Amazon.TVMClient/TemporaryTokenPackage.cs new file mode 100644 index 000000000000..b0dd09526781 --- /dev/null +++ b/Amazon.TVMClient/TemporaryTokenPackage.cs @@ -0,0 +1,23 @@ +/* + * Author: David Iffland, Heavy Code LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + */ + +using System.Net; + +namespace AWSSDK.Amazon.TVMClient +{ + /// + /// Package used to combine necessary information for async callback data manipulation. + /// + public class TemporaryTokenPackage + { + public HttpWebRequest Request { get; set; } + public string DeviceId { get; set; } + } +} \ No newline at end of file