1

I have a Service Bus Relay (WCF SOAP) I want to consume in my Windows Store App. I have written the code to create a token as well as the client which is below.

The problem is that I get an AuthorizationFailedFault returned with a faultstring "InvalidSignature: The token has an invalid signature." And I can't figure it out.

My Create Token method:

private static string CreateSasToken()
{
    TimeSpan sinceEpoch = DateTime.UtcNow - new DateTime(1970,1, 1);
    var expiry = Convert.ToString((int)sinceEpoch.TotalSeconds + 3600);
    string stringToSign = webUtility.UrlEncode(ServiceUri.AbsoluteUri) + "\n" + expiry;

    string hashKey = Encoding.UTF8.GetBytes(Secret).ToString();

    MacAlgorithmProvider macAlgorithmProvider = MacAlgorithmProvider.OpenAlgorithm(MacAlgorithmNames.HmacSha256);
    BinaryStringEncoding encoding = BinaryStringEncoding.Utf8;

    var messageBuffer = CryptographicBuffer.ConvertStringToBinary(stringToSign,encoding);
    IBuffer keyBuffer = CryptographicBuffer.ConvertStringToBinary(hashKey,encoding);

    CryptographicKey hmacKey = macAlgorithmProvider.CreateKey(keyBuffer);
    IBuffer signedMessage = CryptographicEngine.Sign(hmacKey, messageBuffer);

    string signature = CryptographicBuffer.EncodeToBase64String(signedMessage);

    var sasToken = String.Format(CultureInfo.InvariantCulture,
        "SharedAccessSignature sr={0}&sig={1}&se={2}&skn={3}",
        WebUtility.UrlEncode(ServiceUri.AbsoluteUri),
        WebUtility.UrlEncode(signature), expiry, Issuer);

    return sasToken;
}

My Client class:

    public partial class ServiceClient
    {
        public async Task<string> GetDataUsingDataContract(string item, string sasToken)
        {

            HttpClient client = new HttpClient();

            client.DefaultRequestHeaders.Add("ServiceBusAuthorization",sasToken);
            client.DefaultRequestHeaders.Add("SOAPAction",".../GetDataUsingDataContract");
            client.DefaultRequestHeaders.Add("Host", "xxxxxxxxxxx.servicebus.windows.net");

            HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post,ServiceUri);

            var content =new StringContent(@"<s:Envelope
                xmlns:s=""http://schemas.xmlsoap.org/soap/envelope/"">
                <s:Header></s:Header><s:Body>"+ item +@"</s:Body>
                </s:Envelope>",System.Text.Encoding.UTF8,"application/xml");
            request.Content = content;

            HttpResponseMessage wcfResponse = client.SendAsync(request).Result;
            HttpContent stream = wcfResponse.Content;

            var response = stream.ReadAsStringAsync();
            var returnPacket = response.Result;

            return returnPacket;
        }
    }

I have been successful consuming the Relay using Http (via Fiddler) by copying an unexpired token created by Micorosft.ServiceBus in a console app.

John Donnelly
  • 875
  • 1
  • 10
  • 29

1 Answers1

1

I figured out a solution which involved both methods being wrong.

CreateSasToken method:

A minor change involved setting the hashKey variable as byte[] and not string. This line: string hashKey = Encoding.UTF8.GetBytes(Secret).ToString(); Changed to this: var hashKey = Encoding.UTF8.GetBytes(Secret);

This change meant that I needed to use a different method to set keyBuffer. This line: IBuffer keyBuffer = CryptographicBuffer.ConvertStringToBinary(hashKey,encoding); Change to this: IBuffer keyBuffer = CryptographicBuffer.CreateFromByteArray(hashKey);

So the new CreateSasToken method is:

    private static string GetSasToken()
    {
        TimeSpan sinceEpoch = DateTime.UtcNow - new DateTime(1970, 1, 1);
        var expiry = Convert.ToString((int)sinceEpoch.TotalSeconds + 3600);
        string stringToSign = WebUtility.UrlEncode(ServiceUri.AbsoluteUri) + "\n" + expiry;

        var hashKey = Encoding.UTF8.GetBytes(Secret);

        MacAlgorithmProvider macAlgorithmProvider =
            MacAlgorithmProvider.OpenAlgorithm(MacAlgorithmNames.HmacSha256);
        const BinaryStringEncoding encoding = BinaryStringEncoding.Utf8;
        var messageBuffer = CryptographicBuffer.ConvertStringToBinary(stringToSign,
            encoding);

        IBuffer keyBuffer = CryptographicBuffer.CreateFromByteArray(hashKey);
        CryptographicKey hmacKey = macAlgorithmProvider.CreateKey(keyBuffer);
        IBuffer signedMessage = CryptographicEngine.Sign(hmacKey, messageBuffer);

        string signature = CryptographicBuffer.EncodeToBase64String(signedMessage);

        var sasToken = String.Format(CultureInfo.InvariantCulture,
            "SharedAccessSignature sr={0}&sig={1}&se={2}&skn={3}",
            WebUtility.UrlEncode(ServiceUri.AbsoluteUri),
            WebUtility.UrlEncode(signature),
            expiry, Issuer);

        return sasToken;
    }

Service Client Class

A couple of things to note here.

  1. In order for the request to work, the SAS Token had to be added to the header as a parameter of a AuthenticationValueHeader object. So I added the following method to my helper class (ServiceBusHelper) which held the Key, KeyName and SasToken as properties and the CreateSasToken as a method.

    public static AuthenticationHeaderValue CreateBasicHeader()
    {
        return new AuthenticationHeaderValue("Basic", SasToken);
    }
    
  2. The HttpRequestMessage Content property had to be created a special way. Taking the item parameter passed in, which was a serialized WCF DataContract type I needed to do a few things to make the SOAP envelope. Rather than go through them in detail here is the entire class (one method only). I will comment on the code to handle the response immediately following.

    public partial class SalesNotifyServiceClient
    {
        public async Task<string> GetDataUsingDataContract(string item)
        {
            string returnPacket = "";
            string element = "";
            try
            {
                HttpClient client = new HttpClient();
    
                client.DefaultRequestHeaders.Add("ServiceBusAuthorization",
                    ServiceBusHelper.CreateBasicHeader().Parameter);
                client.DefaultRequestHeaders.Add("SOAPAction",
                    ".../GetDataUsingDataContract");
                client.DefaultRequestHeaders.Add("Host",
                    "xxxxxxxxxx.servicebus.windows.net");
    
                HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post,
                    ServiceBusHelper.ServiceUri);
    
                //Creating the request.Content
                var encodedItem = item.Replace("<", "&lt;").Replace(">", "&gt;");
    
                var strRequest =
                    @"<s:Envelope xmlns:s=""http://schemas.xmlsoap.org/soap/envelope/"">
                    <s:Header></s:Header><s:Body><GetDataUsingDataContract xmlns=
                    ""http://www.xxxxxxxxxx.com/servicemodel/relay""><item>" +
                    encodedItem + 
                    @"</item></GetDataUsingDataContract></s:Body></s:Envelope>";
    
                var content = new StringContent(strRequest,
                    System.Text.Encoding.UTF8, "application/xml");
    
                request.Content = content;
    
                HttpResponseMessage wcfResponse = client.SendAsync(request).Result;
                HttpContent stream = wcfResponse.Content;
    
                var response = await stream.ReadAsStringAsync();
    
                //Handling the response
                XDocument doc;
                using (StringReader s = new StringReader(response))
                {
                    doc = XDocument.Load(s);
                }
    
                if (doc.Root != null)
                {
                    element = doc.Root.Value;
                }
    
                returnPacket = element;
            }
            catch (Exception e)
            {
                var message = e.Message;
            }
    
            return returnPacket;
        }
    }
    
  3. In order to get at the DataContract object I had to do a few things to the response string. As you can see at the //Handling the response comment above, using StringReader I loaded the returned SOAP envelope as a string into an XDocument and the root value was my serialized DataContract object. I then deserialized the returnPacket variable returned from the method had my response object.

John Donnelly
  • 875
  • 1
  • 10
  • 29