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