Search code examples
c#restamazon-web-servicestext-to-speech

Ivona Request Signing Issue - signature does not match (AWS Signature Version 4)


I am trying to implement Ivona request signing based on this documnent

Everything works good and all the results match to the example value, except Signature result. So my result for the signature is cf1141e33a8fbba23913f8f36f29faa524a57db37690a1b819f43bbeaabf3b76 but in the document it is equal to 2cdfef28d5c5f6682280600a6141a8940c608cfefacb47f172329cbadb5864cc

Is it my mistake or a mistake in the Ivona document?

Below is the C# code I am using:

class Program
{
    static void Main()
    {
        try
        {
            Console.WriteLine(SendIvonaRequest());
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }

    private static string SendIvonaRequest()
    {
        var date = new DateTime(2013, 09, 13, 09, 20, 54, DateTimeKind.Utc);

        const string algorithm = "AWS4-HMAC-SHA256";
        const string regionName = "eu-west-1";
        const string serviceName = "tts";
        const string method = "POST";
        const string canonicalUri = "/CreateSpeech";
        const string canonicalQueryString = "";
        const string contentType = "application/json";
        const string accessKey = "MyAccessKey";
        const string secretKey = "MySecretKey";

        const string host = serviceName + "." + regionName + ".ivonacloud.com";

        const string requestPayload = "{\"Input\":{\"Data\":\"Hello world\"}}";

        var hashedRequestPayload = HexEncode(Hash(ToBytes(requestPayload)));

        Debug.Assert(hashedRequestPayload.Equals("f43e25253839f2c3feae433c5e477d79f7dfafdc0e4af19a952adb44a60265ba"));

        var dateStamp = date.ToString("yyyyMMdd");
        var requestDate = date.ToString("yyyyMMddTHHmmss") + "Z";
        var credentialScope = string.Format("{0}/{1}/{2}/aws4_request", dateStamp, regionName, serviceName);

        var headers = new SortedDictionary<string, string>
        {
            {"content-type", "application/json"},
            {"host", "tts.eu-west-1.ivonacloud.com"},
            {"x-amz-content-sha256", hashedRequestPayload},
            {"x-amz-date", requestDate}
        };

        string canonicalHeaders =
            string.Join("\n", headers.Select(x => x.Key.ToLowerInvariant() + ":" + x.Value.Trim())) + "\n";
        const string signedHeaders = "content-type;host;x-amz-content-sha256;x-amz-date";

        // Task 1: Create a Canonical Request For Signature Version 4

        var canonicalRequest = method + '\n' + canonicalUri + '\n' + canonicalQueryString +
                               '\n' + canonicalHeaders + '\n' + signedHeaders + '\n' + hashedRequestPayload;

        var hashedCanonicalRequest = HexEncode(Hash(ToBytes(canonicalRequest)));

        Debug.Assert(hashedCanonicalRequest.Equals("73ff17c0bf9da707afb02bbceb77d359ab945a460b5ac9fff7a0a61cfaab95e6"));

        // Task 2: Create a String to Sign for Signature Version 4
        // StringToSign  = Algorithm + '\n' + RequestDate + '\n' + CredentialScope + '\n' + HashedCanonicalRequest

        var stringToSign = string.Format("{0}\n{1}\n{2}\n{3}", algorithm, requestDate, credentialScope,
            hashedCanonicalRequest);

        Debug.Assert(stringToSign.Equals("AWS4-HMAC-SHA256" + "\n" +
                                         "20130913T092054Z" + "\n" +
                                         "20130913/eu-west-1/tts/aws4_request" + "\n" +
                                         "73ff17c0bf9da707afb02bbceb77d359ab945a460b5ac9fff7a0a61cfaab95e6"));

        // Task 3: Calculate the AWS Signature Version 4

        // HMAC(HMAC(HMAC(HMAC("AWS4" + kSecret,"20130913"),"eu-west-1"),"tts"),"aws4_request")
        byte[] signingKey = GetSignatureKey(secretKey, dateStamp, regionName, serviceName);

        // signature = HexEncode(HMAC(derived-signing-key, string-to-sign))
        var signature = HexEncode(HmacSha256(stringToSign, signingKey));

        Debug.Assert(signature.Equals("2cdfef28d5c5f6682280600a6141a8940c608cfefacb47f172329cbadb5864cc"));

        // Task 4: Prepare a signed request
        // Authorization: algorithm Credential=access key ID/credential scope, SignedHeadaers=SignedHeaders, Signature=signature

        var authorization =
            string.Format("{0} Credential={1}/{2}/{3}/{4}/aws4_request, SignedHeaders={5}, Signature={6}",
                algorithm, accessKey, dateStamp, regionName, serviceName, signedHeaders, signature);

        // Send the request

        var webRequest = WebRequest.Create("https://" + host + canonicalUri);

        webRequest.Method = method;
        webRequest.Timeout = 2000;
        webRequest.ContentType = contentType;
        webRequest.Headers.Add("X-Amz-date", requestDate);
        webRequest.Headers.Add("Authorization", authorization);
        webRequest.Headers.Add("x-amz-content-sha256", hashedRequestPayload);
        webRequest.ContentLength = requestPayload.Length;

        using (Stream newStream = webRequest.GetRequestStream())
        {
            newStream.Write(ToBytes(requestPayload), 0, requestPayload.Length);
        }

        var response = (HttpWebResponse) webRequest.GetResponse();

        using (Stream responseStream = response.GetResponseStream())
        {
            if (responseStream != null)
            {
                using (var streamReader = new StreamReader(responseStream))
                {
                    return streamReader.ReadToEnd();
                }
            }
        }

        return string.Empty;
    }

    private static byte[] GetSignatureKey(String key, String dateStamp, String regionName, String serviceName)
    {
        byte[] kDate = HmacSha256(dateStamp, ToBytes("AWS4" + key));
        byte[] kRegion = HmacSha256(regionName, kDate);
        byte[] kService = HmacSha256(serviceName, kRegion);
        return HmacSha256("aws4_request", kService);
    }

    private static byte[] ToBytes(string str)
    {
        return Encoding.UTF8.GetBytes(str.ToCharArray());
    }

    private static string HexEncode(byte[] bytes)
    {
        return BitConverter.ToString(bytes).Replace("-", string.Empty).ToLowerInvariant();
    }

    private static byte[] Hash(byte[] bytes)
    {
        var sha256 = SHA256.Create();
        return sha256.ComputeHash(bytes);
    }

    private static byte[] HmacSha256(String data, byte[] key)
    {
        return new HMACSHA256(key).ComputeHash(ToBytes(data));
    }
}

UPD1:

I have also tried the examples from http://docs.aws.amazon.com/general/latest/gr/sigv4_signing.html and noticed that my code generate the same signature as in those examples. So I am assuming that there is an issue in Ivona document...

UPD2:

Everything works fine! I have implemented CreateSpeech method according to the description and uploaded a full example of the usage to GitHub https://github.com/MalyutinS/DotNetIvonaAPI


Solution

  • Solved! Actually there is an issue in documentation example. So the code works fine.