I am working in a cross platform project, where we need to connect to Azure Notification Hub from Unity 3d.
We are trying to use the REST API, but we are having a problem with the generation of the SAS token. When trying to write to the stream we get an exception "Error writing request: The authentication or decryption has failed."
I think the generation of the SaS token has something wrong since it had to be modified to be cross platform and work within Unity.
Here is the code so far
using UnityEngine;
using System.Collections;
using System;
using System.Net;
using System.IO;
using System.Text;
/// <summary>
/// check https://msdn.microsoft.com/en-us/library/azure/dn495627.aspx
/// check also http://piotrwalat.net/hmac-authentication-in-asp-net-web-api/
/// </summary>
public class AzureNotificationHub : MonoBehaviour
{
public string CloudServiceNotificationHubPath = "cloudservicechat";
public string CloudServiceNotificationHubConnectionString =
"{CONNSTRINGHERE}";
public string NotificationHubUrl = "{HUBURLHERE}";
private ConnectionStringUtility objConnectionStringUtility = null;
public string SasToken;
public TextAsset CreateRegistrationTemplate = null;
// Use this for initialization
void Start()
{
this.objConnectionStringUtility = new ConnectionStringUtility(CloudServiceNotificationHubConnectionString);
this.SasToken = this.objConnectionStringUtility.getSaSToken(this.NotificationHubUrl, 10);
}
// Update is called once per frame
void Update()
{
}
private string atomContentType = "application/atom+xml;type=entry;charset=utf-8";
public void CreateRegistration()
{
string methodUrl = string.Format("{0}/{1}", this.NotificationHubUrl, "registrations/?api-version=2015-01");
string result = HttpPost(methodUrl);
}
/// <summary>
/// check http://stackoverflow.com/questions/9153181/adding-a-body-to-a-httpwebrequest-that-is-being-used-with-the-azure-service-mgmt
/// </summary>
/// <param name="url"></param>
/// <returns></returns>
string HttpGet(string url)
{
HttpWebRequest req = WebRequest.Create(url)
as HttpWebRequest;
req.Headers.Add("Content-Type", this.atomContentType);
req.Headers.Add("Authorization", this.SasToken);
req.Headers.Add("x-ms-version", "2015-01");
req.Method = WebRequestMethods.Http.Get;
string result = null;
//writeStream.Write
using (HttpWebResponse resp = req.GetResponse()
as HttpWebResponse)
{
StreamReader reader =
new StreamReader(resp.GetResponseStream());
result = reader.ReadToEnd();
}
return result;
}
string HttpPost(string url)
{
string result = null;
string tokenForUrl = this.objConnectionStringUtility.getSaSToken(url, 10);
try
{
string body = this.CreateRegistrationTemplate.text;
body = body.Replace("{tags}", string.Empty);
body = body.Replace("{ChannelUri}", this.NotificationHubUrl);
byte[] bodyArray = System.Text.Encoding.UTF8.GetBytes(body);
HttpWebRequest req = WebRequest.Create(url)
as HttpWebRequest;
req.ContentType = this.atomContentType;
//req.Headers.Add(HttpRequestHeader.Authorization, this.SasToken);
req.Headers.Add(HttpRequestHeader.Authorization, tokenForUrl);
req.Headers.Add("x-ms-version", "2015-01");
req.Method = WebRequestMethods.Http.Post;
Stream writeStream = req.GetRequestStream();
writeStream.Write(bodyArray, 0, bodyArray.Length);
writeStream.Flush();
writeStream.Close();
using (HttpWebResponse resp = req.GetResponse()
as HttpWebResponse)
{
StreamReader reader =
new StreamReader(resp.GetResponseStream());
result = reader.ReadToEnd();
}
}
catch (Exception ex)
{
result = ex.Message;
}
return result;
}
}
public partial class ConnectionStringUtility
{
public string Endpoint { get; private set; }
public string SasKeyName { get; private set; }
public string SasKeyValue { get; private set; }
private string sasTokenn { get; set; }
public ConnectionStringUtility(string connectionString)
{
//Parse Connectionstring
char[] separator = { ';' };
string[] parts = connectionString.Split(separator);
for (int i = 0; i < parts.Length; i++)
{
if (parts[i].StartsWith("Endpoint"))
Endpoint = "https" + parts[i].Substring(11);
if (parts[i].StartsWith("SharedAccessKeyName"))
SasKeyName = parts[i].Substring(20);
if (parts[i].StartsWith("SharedAccessKey"))
SasKeyValue = parts[i].Substring(16);
}
}
/// <summary>
/// check http://buchananweb.co.uk/security01i.aspx
/// </summary>
/// <param name="uri"></param>
/// <param name="minUntilExpire"></param>
/// <returns></returns>
public string getSaSToken(string uri, int minUntilExpire)
{
string targetUri = Uri.EscapeDataString(uri.ToLower()).ToLower();
// Add an expiration in seconds to it.
long expiresOnDate = DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond;
expiresOnDate += minUntilExpire * 60 * 1000;
long expires_seconds = expiresOnDate / 1000;
String toSign = targetUri + "\n" + expires_seconds;
HmacSignatureCalculator hmacSigner = new HmacSignatureCalculator();
System.Security.Cryptography.SHA256Managed crypto = new System.Security.Cryptography.SHA256Managed();
System.Text.UTF8Encoding encoding = new System.Text.UTF8Encoding();
var messageBuffer = ConvertStringToBinary(toSign, encoding);
var keyBuffer = ConvertStringToBinary(SasKeyValue, encoding);
var hmacKey = CreateKey(keyBuffer);
var signedMessage = Sign(hmacKey, messageBuffer);
string signature = Uri.EscapeDataString(signedMessage);
return "SharedAccessSignature sr=" + targetUri + "&sig=" + signature + "&se=" + expires_seconds + "&skn=" + SasKeyName;
}
private byte[] CreateKey(byte[] keyBuffer)
{
System.Security.Cryptography.SHA256Managed crypto = new System.Security.Cryptography.SHA256Managed();
var computedHash = crypto.ComputeHash(keyBuffer);
return computedHash;
}
public string Sign(byte[] hmacKey, byte[] messageBuffer)
{
using (System.Security.Cryptography.SHA256Managed crypto = new System.Security.Cryptography.SHA256Managed())
{
var hash = crypto.ComputeHash(hmacKey);
var signature = Convert.ToBase64String(hash);
return signature;
}
}
public byte[] ConvertStringToBinary(string value, Encoding encoding)
{
//Requires.NotNull(value, "value");
//Requires.NotNull(encoding, "encoding");
return encoding.GetBytes(value);
}
}
Any ideas what I should do? Thanks for the help.