amzn / selling-partner-api-models

This repository contains OpenAPI models for developers to use when developing software to call Selling Partner APIs.
Apache License 2.0
618 stars 741 forks source link

C# AWS4 Signature Issue #1969

Closed msbeden closed 2 years ago

msbeden commented 3 years ago

Hello there, I started writing SP API. I have reached this stage: https://github.com/amzn/selling-partner-api-docs/blob/main/guides/en-US/developer-guide/SellingPartnerApiDeveloperGuide.md#step-4-create-and-sign-your-request

I am working in C#. But I couldn't understand the "Authorization" parameter. How can I calculate the "Signature" value in it?

Can you suggest me the method you used?

I run this but the signature is doing it wrong.

using System;
using System.Web;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Security.Cryptography;
using System.Text;
namespace AWS_Signature
{
    public class Program
    {
        public static string url = "https://sellingpartnerapi-eu.amazon.com/fba/inbound/v0/shipments/shipmentId1/preorder/confirm?MarketplaceId=A33AVAJ2PDY3EV&NeedByDate=2021-10-22";
        public static string accessKey = "AKIA....";
        public static string secretkey = "19U9....";
        public static string awsRegion = "eu-west-1";
        public static string awsServiceName = "execute-api";
        public static string xApiKey = "Atza|IwEBIHObxuNXvc0Nr....";

        public static void Main(string[] args)
        {
            // 0. Prepare request message.
            HttpRequestMessage msg = new HttpRequestMessage(HttpMethod.Get, url);
            msg.Headers.Host = msg.RequestUri.Host;

            // Get and save dates ready for further use.
            DateTimeOffset utcNowSaved = DateTimeOffset.UtcNow;
            string amzLongDate = utcNowSaved.ToString("yyyyMMddTHHmmssZ");
            string amzShortDate = utcNowSaved.ToString("yyyyMMdd");

            // Add to headers. 
            msg.Headers.Add("x-amz-date", amzLongDate);
            msg.Headers.Add("x-api-key", xApiKey); // My API call needs an x-api-key passing also for function security.

            // **************************************************** SIGNING PORTION ****************************************************
            // 1. Create Canonical Request            
            var canonicalRequest = new StringBuilder();
            canonicalRequest.Append(msg.Method + "\n");
            canonicalRequest.Append(string.Join("/", msg.RequestUri.AbsolutePath.Split('/').Select(Uri.EscapeDataString)) + "\n");

            canonicalRequest.Append(GetCanonicalQueryParams(msg) + "\n"); // Query params to do.

            var headersToBeSigned = new List<string>();
            foreach (var header in msg.Headers.OrderBy(a => a.Key.ToLowerInvariant(), StringComparer.OrdinalIgnoreCase))
            {
                canonicalRequest.Append(header.Key.ToLowerInvariant());
                canonicalRequest.Append(":");
                canonicalRequest.Append(string.Join(",", header.Value.Select(s => s.Trim())));
                canonicalRequest.Append("\n");
                headersToBeSigned.Add(header.Key.ToLowerInvariant());
            }
            canonicalRequest.Append("\n");

            var signedHeaders = string.Join(";", headersToBeSigned);
            canonicalRequest.Append(signedHeaders + "\n");
            canonicalRequest.Append("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"); // Signature for empty body.
            // Hash(msg.Content.ReadAsByteArrayAsync().Result);

            // 2. String to sign.            
            string stringToSign = "AWS4-HMAC-SHA256" + "\n" + amzLongDate + "\n" + amzShortDate + "/" + awsRegion + "/" + awsServiceName + "/aws4_request" + "\n" + Hash(Encoding.UTF8.GetBytes(canonicalRequest.ToString()));

            // 3. Signature with compounded elements.
            var dateKey = HmacSha256(Encoding.UTF8.GetBytes("AWS4" + secretkey), amzShortDate);
            var dateRegionKey = HmacSha256(dateKey, awsRegion);
            var dateRegionServiceKey = HmacSha256(dateRegionKey, awsServiceName);
            var signingKey = HmacSha256(dateRegionServiceKey, "aws4_request");

            var signature = ToHexString(HmacSha256(signingKey, stringToSign.ToString()));

            // **************************************************** END SIGNING PORTION ****************************************************

            // Add the Header to the request.
            var credentialScope = amzShortDate + "/" + awsRegion + "/" + awsServiceName + "/aws4_request";
            msg.Headers.TryAddWithoutValidation("Authorization", "AWS4-HMAC-SHA256 Credential=" + accessKey + "/" + credentialScope + ", SignedHeaders=" + signedHeaders + ", Signature=" + signature);

            // Invoke the request with HttpClient.
            HttpClient client = new HttpClient();
            HttpResponseMessage result = client.SendAsync(msg).Result;
            if (result.IsSuccessStatusCode)
            {
                // Inspect the result and payload returned.
                Console.WriteLine(result.Headers);
                Console.WriteLine(result.Content.ReadAsStringAsync().Result);
                // Wait on user.
                Console.ReadLine();
            }
            else
            {
                Console.WriteLine(result.RequestMessage);
            }
        }
        private static string GetCanonicalQueryParams(HttpRequestMessage request)
        {
            var values = new SortedDictionary<string, string>();

            var querystring = HttpUtility.ParseQueryString(request.RequestUri.Query);
            foreach (var key in querystring.AllKeys)
            {
                if (key == null)//Handles keys without values
                {
                    values.Add(Uri.EscapeDataString(querystring[key]), $"{Uri.EscapeDataString(querystring[key])}=");
                }
                else
                {
                    // Escape to upper case. Required.
                    values.Add(Uri.EscapeDataString(key), $"{Uri.EscapeDataString(key)}={Uri.EscapeDataString(querystring[key])}");
                }
            }
            // Put in order - this is important.
            var queryParams = values.Select(a => a.Value);
            return string.Join("&", queryParams);
        }

        public static string Hash(byte[] bytesToHash)
        {
            return ToHexString(SHA256.Create().ComputeHash(bytesToHash));
        }
        private static string ToHexString(IReadOnlyCollection<byte> array)
        {
            var hex = new StringBuilder(array.Count * 2);
            foreach (var b in array)
            {
                hex.AppendFormat("{0:x2}", b);
            }
            return hex.ToString();
        }

        private static byte[] HmacSha256(byte[] key, string data)
        {
            return new HMACSHA256(key).ComputeHash(Encoding.UTF8.GetBytes(data));
        }
    }
}

amazon_1 amazon_2

abuzuhri commented 3 years ago

Try to Use my library handle most of requirement https://github.com/abuzuhri/Amazon-SP-API-CSharp

github-actions[bot] commented 2 years ago

This is a very old issue that is probably not getting as much attention as it deserves. We encourage you to check if this is still an issue after the latest release and if you find that this is still a problem, please feel free to open a new issue and make a reference to this one.