AWS API Gateway Signature

2019-02-11 09:05发布

问题:

I am trying to sign my requests to the amazon gateway. But every time when I try to send a POST request it tells me that my signature has been expired. Any ideas will be appreciated.

回答1:

You have some problem with getting the time or something like that. I had the problem with the payload. So if you are making GET request your payload is an EMPTY STRING. Otherwise it should be hashed Json object. Here is example of how I do it in my application. The code can be raw, but I am 100000% it work, because I am using it every day.

const string RegionName = "eu-west-1"; //This is the regionName
const string ServiceName = "apigateway";
const string Algorithm = "AWS4-HMAC-SHA256";
const string ContentType = "application/json";
const string Host = "apigateway.eu-west-1.amazonaws.com";
const string SignedHeaders = "content-type;host;x-amz-date";

public static WebRequest RequestGet(string canonicalUri, string canonicalQueriString, string jsonString) {
    string hashedRequestPayload = CreateRequestPayload("");

    string authorization = Sign(hashedRequestPayload, "GET", canonicalUri, canonicalQueriString);
    string requestDate = DateTime.UtcNow.ToString("yyyyMMddTHHmmss") + "Z";

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

    webRequest.Method = "GET";
    webRequest.ContentType = ContentType;
    webRequest.Headers.Add("X-Amz-date", requestDate);
    webRequest.Headers.Add("Authorization", authorization);
    webRequest.Headers.Add("x-amz-content-sha256", hashedRequestPayload);


    return webRequest;
}

public static WebRequest RequestPost(string canonicalUri, string canonicalQueriString, string jsonString)
{
    string hashedRequestPayload = CreateRequestPayload(jsonString);

    string authorization = Sign(hashedRequestPayload, "POST", canonicalUri, canonicalQueriString);
    string requestDate = DateTime.UtcNow.ToString("yyyyMMddTHHmmss") + "Z";

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

    webRequest.Timeout = 20000;
    webRequest.Method = "POST";
    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 = jsonString.Length;

    ASCIIEncoding encoding = new ASCIIEncoding();
    byte[] data = encoding.GetBytes(jsonString);

    Stream newStream = webRequest.GetRequestStream();
    newStream.Write(data, 0, data.Length);


    return webRequest;
}

private static string CreateRequestPayload(string jsonString) {
    //Here should be JSON object of the model we are sending with POST request
    //var jsonToSerialize = new { Data = String.Empty };

    //We parse empty string to the serializer if we are makeing GET request
    //string requestPayload = new JavaScriptSerializer().Serialize(jsonToSerialize);
    string hashedRequestPayload = HexEncode(Hash(ToBytes(jsonString)));

    return hashedRequestPayload;
}

private static string Sign(string hashedRequestPayload, string requestMethod, string canonicalUri, string canonicalQueryString) {
    var currentDateTime = DateTime.UtcNow;
    var accessKey = //Here place your app ACCESS_KEY
    var secretKey = //Here is a place for you app SECRET_KEY

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

    var headers = new SortedDictionary < string, string > {
            { "content-type", ContentType },
            { "host", Host  }, 
            { "x-amz-date", requestDate }
        };

    string canonicalHeaders = string.Join("\n", headers.Select(x => x.Key.ToLowerInvariant() + ":" + x.Value.Trim())) + "\n";

    // Task 1: Create a Canonical Request For Signature Version 4
    string canonicalRequest = requestMethod + "\n" + canonicalUri + "\n" + canonicalQueryString + "\n" + canonicalHeaders + "\n" + SignedHeaders + "\n" + hashedRequestPayload;
    string hashedCanonicalRequest = HexEncode(Hash(ToBytes(canonicalRequest)));

    // Task 2: Create a String to Sign for Signature Version 4
    string stringToSign = Algorithm + "\n" + requestDate + "\n" + credentialScope + "\n" + hashedCanonicalRequest;

    // Task 3: Calculate the AWS Signature Version 4
    byte[] signingKey = GetSignatureKey(secretKey, dateStamp, RegionName, ServiceName);
    string signature = HexEncode(HmacSha256(stringToSign, signingKey));

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

    string authorization = string.Format("{0} Credential={1}/{2}/{3}/{4}/aws4_request, SignedHeaders={5}, Signature={6}",
    Algorithm, accessKey, dateStamp, RegionName, ServiceName, SignedHeaders, signature);

    return authorization;
}

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) {
    return SHA256.Create().ComputeHash(bytes);
}

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

So for example if I want to get all the APIs that are deployed in the Gateway I am doing like this:

using(WebResponse response = webRequest.GetResponse()) {
    StreamReader responseReader = new   StreamReader(response.GetResponseStream());
    string responseJson = responseReader.ReadToEnd();
} catch (WebException) {
    //Doing something when exception has been thrown
}

Here is the interesting part of creating a API Key. First you need to make your raw payload and then pass it to the methods I gave you above:

string payload = "{ \"name\" : \"" + name + "\", \"description\" : \"" + description.Trim() + "\", \"enabled\" : \"True\", \"stageKeys\" : [ ] }";

WebRequest webRequest = RequestSignerAWS.RequestPost("/apikeys", "", payload);

And make sure you are getting the time of the creating the request, because this will cause you the problem you are having.