Search code examples
amazon-web-serviceshashencodesignature

Amazon Web service - signature


I've been receiving an error from Amazon web service

InvalidParameterValue
Either Action or Operation query parameter must be present.

I believe it is most likely due to the signature being incorrect as the XML document and Header matches that of a test I did in their scratchpad.

Does anything stand out as being incorrect?

private static string ConstructCanonicalQueryString(SortedDictionary<string, string> sortedParameters)
{
    var builder = new StringBuilder();

    if (sortedParameters.Count == 0)
    {
        builder.Append(string.Empty);
        return builder.ToString();
    }

    foreach (var kvp in sortedParameters)
    {
        builder.Append(PercentEncodeRfc3986(kvp.Key));
        builder.Append("=");
        builder.Append(PercentEncodeRfc3986(kvp.Value));
        builder.Append("&");
    }

    var canonicalString = builder.ToString();
    return canonicalString.Substring(0, canonicalString.Length - 1);
}


private static string PercentEncodeRfc3986(string value)
{
    value = HttpUtility.UrlEncode(string.IsNullOrEmpty(value) ? string.Empty : value, Encoding.UTF8);

    if (string.IsNullOrEmpty(value))
    {
        return string.Empty;
    }

    value = value.Replace("'", "%27")
            .Replace("(", "%28")
            .Replace(")", "%29")
            .Replace("*", "%2A")
            .Replace("!", "%21")
            .Replace("%7e", "~")
            .Replace("+", "%20")
            .Replace(":", "%3A");

    var sbuilder = new StringBuilder(value);

    for (var i = 0; i < sbuilder.Length; i++)
    {
        if (sbuilder[i] != '%')
        {
            continue;
        }

        if (!char.IsLetter(sbuilder[i + 1]) && !char.IsLetter(sbuilder[i + 2]))
        {
            continue;
        }

        sbuilder[i + 1] = char.ToUpper(sbuilder[i + 1]);
        sbuilder[i + 2] = char.ToUpper(sbuilder[i + 2]);
    }

    return sbuilder.ToString();
}

public string SignRequest(Dictionary<string, string> parametersUrl, Dictionary<string, string> 

parametersSignture)
{
    var secret = Encoding.UTF8.GetBytes(parametersSignture["Secret"]);
    var signer = new HMACSHA256(secret);

    var pc = new ParamComparer();
    var sortedParameters = new SortedDictionary<string, string>(parametersUrl, pc);
    var orderedParameters = ConstructCanonicalQueryString(sortedParameters);
         
    var builder = new StringBuilder();
    builder.Append(parametersSignture["RequestMethod"])
            .Append(" \n")
            .Append(parametersSignture["EndPoint"])
            .Append("\n")
            .Append("/\n")
            .Append(orderedParameters);

    var stringToSign = builder.ToString();
    var toSign = Encoding.UTF8.GetBytes(stringToSign);

    var sigBytes = signer.ComputeHash(toSign);
    var signature = Convert.ToBase64String(sigBytes);

    return signature.Replace("=", "%3D").Replace("/", "%2F").Replace("+", "%2B");
}

public class ParamComparer : IComparer<string>
{
    public int Compare(string p1, string p2)
    {
        return string.CompareOrdinal(p1, p2);
    }
}

Solution

  • The issue was that the Action wasn't included correctly into the Request