使用签名调用 Api 网关时出现错误消息
Error message when calling Api Gateway with signature
这是我遇到的错误....
The request signature we calculated does not match the signature you provided. Check your AWS Secret Access Key and signing method. Consult the service documentation for details.
The Canonical String for this request should have been
'GET
/devcam/securehello
host:*.execute-api.us-west-2.amazonaws.com
x-amz-date:20161223T170922Z
host;x-amz-date
e3b0c44098098s098df098s...8f0s98df098sd09f8s0d98f095'
The String-to-Sign should have been
'AWS4-HMAC-SHA256
20161223T170922Z
20161223/us-west-2/execute-api/aws4_request
828177fca39making23409...9840928304980238402983282d'
奇怪的是我将它与我的 Canonical String 和 String to Sign 进行比较,它们是相同的...我还需要检查什么?
我正在尝试使用临时 aws lambda 凭据调用它。我不确定我应该如何使用 Session 令牌,但现在它被添加为 header (我试过将它包含在签名的 header 中并且不包括它——两者都不起作用)"X-Amz-Security-Token".
编辑:
这是我用来发送请求的来自 .NET 核心的 JSON 序列化 HttpMessageRequest object。
{
"Version": {
"Major": 1,
"Minor": 1,
"Build": -1,
"Revision": -1,
"MajorRevision": -1,
"MinorRevision": -1
},
"Content": null,
"Method": {
"Method": "GET"
},
"RequestUri": "https:\/\/somerandomapigatewaystring.execute-api.us-west-2.amazonaws.com\/devcam\/securehello",
"Headers": [
{
"Key": "x-amz-security-token",
"Value": [
"FQoDYXdzEKr\/\/\/\/\/\/\/\/\/\/wEaDPuFLZoHsomefmorefakevaluesin5here0+36yIwO1kh8sdkeuf98eek3kj4934898erjthisisafakevaluehahahahahahahahaz8FE7jKvENO1IdshzqjDFWhpnfLESAS4B404dBW\/VAkH1jJDvpiFylP5qiZk6q+S\/eJHDdLUoVAXDqkh0w2wkFdXNpsMsfr8eLdzXP8jT\/ZDA+OxG2qzxd1UaotDl\/\/NSzWjNyOYGX3pEoNk9LtefKBGKbgTvH5lrguydfN8JFb4mGjtP8wRY7tjHHq8jtLQhJMRiub3eGnR\/vxwNQk8EJ5usJxB1UF2Kl0p5LqSohViivqfXCBQ=="
]
},
{
"Key": "host",
"Value": [
"somerandomapigatewaystring.execute-api.us-west-2.amazonaws.com"
]
},
{
"Key": "x-amz-date",
"Value": [
"20161223T200041Z"
]
},
{
"Key": "Authorization",
"Value": [
"AWS4-HMAC-SHA256 Credential=********************\/20161223\/us-west-2\/execute-api\/aws4_request, SignedHeaders=host;x-amz-date, Signature=3e0d3***********************************************************"
]
}
],
"Properties": {
}
}
这是我的代码 -- 对于混乱,我深表歉意。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
using Amazon.CloudFormation;
using Amazon.IdentityManagement;
using Amazon.IdentityManagement.Model;
using Amazon.Lambda.Core;
using Amazon.Runtime.Internal;
using Amazon.Runtime.Internal.Auth;
using Amazon.SecurityToken;
using Amazon.SecurityToken.Model;
using bifrost;
using Newtonsoft.Json;
namespace Bifrost
{
public class Handler
{
private static HttpClient httpClient = new HttpClient(); //yes we want this to be static https://aspnetmonsters.com/2016/08/2016-08-27-httpclientwrong/
private static string Service = "execute-api";
// private static string Service = "apigateway";
private static string Algorithm = "AWS4-HMAC-SHA256";
public const string Iso8601DateTimeFormat = "yyyyMMddTHHmmssZ";
public const string Iso8601DateFormat = "yyyyMMdd";
public Response Hello(Request request, ILambdaContext context)
{
try
{
var now = DateTime.Now;
var signedHeaders = new string[]
{
// "content-type",
"host", //http://docs.aws.amazon.com/general/latest/gr/rande.html#apigateway_region
"x-amz-date",
// "x-amz-security-token",
// "Authorization",
// "Credential"
};
//X-Amz-Security-Token
HttpRequestMessage httpRequest = new HttpRequestMessage(HttpMethod.Get, "https://somerandomapigatewaystring.execute-api.us-west-2.amazonaws.com/devcam/securehello");
httpRequest.Headers.Add("x-amz-security-token", EnvironmentVariables.SessionToken);
// httpRequest.Headers.Add("host", "apigateway.us-west-2.amazonaws.com");
httpRequest.Headers.Add("host", "somerandomapigatewaystring.execute-api.us-west-2.amazonaws.com");
httpRequest.Headers.Add("x-amz-date", now.ToString(Iso8601DateTimeFormat));
// httpRequest.Content.Headers.ContentType = MediaTypeHeaderValue.Parse("application/json");
// httpRequest.Headers.Add("X-Amz-Security-Token", EnvironmentVariables.SessionToken);
string credentialScope = String.Format("{0}/{1}/{2}/aws4_request", now.ToString(Iso8601DateFormat),
EnvironmentVariables.Region, Service);
string credentialString = EnvironmentVariables.AccessKeyId + "/" + credentialScope;
string signedHeadersString = string.Join(";",
signedHeaders.Select(x => x.Trim().ToLowerInvariant()).OrderBy(x => x));
// AwsV4SignatureCalculator signatureCalculator = new AwsV4SignatureCalculator(EnvironmentVariables.SecretKey, Service, "us-west-2");
// string signature = signatureCalculator.CalculateSignature(httpRequest, signedHeaders, now);
string signature = getSignature(EnvironmentVariables.AccessKeyId, Service, "us-west-2", httpRequest, signedHeaders, now);
// task 4: add signing info to request
string authorization = CreateAuthorizationGET(signature, signedHeadersString, credentialScope);
Console.WriteLine("authorization:");
Console.WriteLine(authorization);
// The format of value 'AWS4-HMAC-SHA256 Credential=/20161222/us-west-2/execute-api/aws4_request, SignedHeaders=host;x-amz-date;x-amz-security-token, Signature=.
// httpRequest.Headers.Add("Authorization", authorization);
httpRequest.Headers.TryAddWithoutValidation("Authorization", authorization);
// httpRequest.Headers.TryAddWithoutValidation("Authorization", authorization + ", X-Amz-Security-Token=" + EnvironmentVariables.SessionToken);
// httpRequest.Version = Version./
var response = httpClient.SendAsync(httpRequest);
return new Response
{
Body =
"Go Serverless v1.0! Your function executed successfully!\r\n" +
JsonConvert.SerializeObject(request)
+ "\r\n\r\n" + JsonConvert.SerializeObject(context) + "\r\n\r\n" +
JsonConvert.SerializeObject(Environment.GetEnvironmentVariables()) + "\r\n\r\n" +
JsonConvert.SerializeObject(httpRequest) + "\r\n\r\n" +
JsonConvert.SerializeObject(response.Result) + "\r\n\r\n" +
// "",
response.Result.Content.ReadAsStringAsync().Result,
StatusCode = 200,
Headers = new Dictionary<string, string>()
};
}
catch (Exception e)
{
return new Response
{
StatusCode = 200,
Body = e.Message + "\r\n\r\n" + e.StackTrace +
"\r\n\r\n" + e.InnerException.StackTrace,
Headers = new Dictionary<string, string>()
};
}
}
private string getSignature(string accessKey, string service, string region, HttpRequestMessage httpRequest, string[] signedHeaders, DateTime now)
{
// httpRequest.Content = new StringContent("", Encoding.UTF8,
// "application/json");
var signedHeadersDict = createSignedHeadersDictionary(signedHeaders, httpRequest);
string hashedPayload = AWSAuthenticationHelper.HashAndHexEncodeString("");
string canonicalHeaders = string.Join("\n", signedHeadersDict.Select(x => x.Key.ToLowerInvariant() + ":" + x.Value.Trim())) + "\n";
string signedHeadersString = string.Join(";", signedHeaders);
// httpRequest.Headers.Add("X-Amz-SignedHeaders", signedHeadersString);
string canonicalRequest = AWSAuthenticationHelper.CreateCanconicalRequestGET(hashedPayload, signedHeadersString, canonicalHeaders,
httpRequest.RequestUri.AbsolutePath,
httpRequest.RequestUri.Query);
Console.WriteLine("Canonical String:");
Console.WriteLine(canonicalRequest);
string hashedCanonicalRequest = AWSAuthenticationHelper.HashAndHexEncodeString(canonicalRequest);
string dateStamp = now.ToString(Iso8601DateFormat);
string dateTimeStamp = now.ToString(Iso8601DateTimeFormat);
// httpRequest.Headers.Add("X-Amz-Date", now.ToString(Iso8601DateTimeFormat));
string credentialScope = String.Format("{0}/{1}/{2}/aws4_request", dateStamp, region, service);
// httpRequest.Headers.Add("X-Amz-Credential", credentialScope);
string stringToSign = CreateStringToSign(hashedCanonicalRequest, dateTimeStamp, credentialScope);
Console.WriteLine("stringToSign:");
Console.WriteLine(stringToSign);
// task 3: calculate signature
string signature = AWSAuthenticationHelper.CreateSignature(stringToSign, dateStamp, region, service, accessKey);
return signature;
}
private string CreateAuthorizationGET(string signature, string signedHeaders, string credentialScope)
{
return String.Format("{0} Credential={1}/{2}, SignedHeaders={3}, Signature={4}", Algorithm, EnvironmentVariables.AccessKeyId, credentialScope, signedHeaders, signature);
}
private string CreateStringToSign(string hashedRequest, string requestDate, string credentialScope)
{
string stringToSign = string.Format("{0}\n{1}\n{2}\n{3}", Algorithm, requestDate, credentialScope, hashedRequest);
return stringToSign;
}
private SortedDictionary<string, string> createSignedHeadersDictionary(string[] signedHeaders, HttpRequestMessage httpRequest)
{
SortedDictionary<string, string> ret = new SortedDictionary<string, string>();
List<string> signedHeadersList = signedHeaders.Select(x => x.ToLowerInvariant().Trim()).OrderBy(x => x).ToList();
foreach (KeyValuePair<string, IEnumerable<string>> header in httpRequest.Headers)
{
if (signedHeadersList.Contains(header.Key.ToLowerInvariant().Trim()))
{
ret.Add(header.Key, String.Join(",", header.Value)); // TODO: is tostring safe here?
}
}
if (httpRequest.Content != null && httpRequest.Content.Headers != null)
{
foreach (var contentHeader in httpRequest.Content.Headers)
{
if (signedHeaders.Contains(contentHeader.Key.ToLowerInvariant().Trim()))
{
ret.Add(contentHeader.Key, string.Join(",", contentHeader.Value));
}
}
}
// else if (signedHeadersList.Contains("content-type"))
// {
// throw new Exception("content-type header set but no Content found");
// }
return ret;
}
public Response SecureHello(Request request, ILambdaContext context)
{
return new Response
{
Body = "securely called!\r\n" + JsonConvert.SerializeObject(request)
+ "\r\n\r\n" + JsonConvert.SerializeObject(context) + "\r\n\r\n" +
JsonConvert.SerializeObject(Environment.GetEnvironmentVariables()),
StatusCode = 200,
Headers = new Dictionary<string, string>()
};
}
}
public class Response : BaseResponse
{
}
public class Request : BaseRequest
{
}
}
还有更多代码...
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
namespace bifrost
{
public class AWSAuthenticationHelper
{
public static string HashAndHexEncodeString(string str)
{
return HexEncode(GenerateHash(ToBytes(str)));
}
public static string CreateSignature(string stringToSign, string dateStamp, string region, string serviceName, string awsKey)
{
byte[] key = GenerateKey(dateStamp, region, serviceName, awsKey);
return HexEncode(HmacSha256(stringToSign, key));
}
public static string CreateCanconicalRequestPOST(string hashedPayload, string signedHeaders, string canonicalHeaders, string canonicalURI)
{
string canonicalRequest = "POST" + "\n" + canonicalURI + "\n" + "" + "\n" + canonicalHeaders + "\n" +
signedHeaders + "\n" + hashedPayload;
return canonicalRequest;
}
public static string CreateCanconicalRequestGET(string hashedPayload, string signedHeaders, string canonicalHeaders, string canonicalURI, string canonicalQueryString)
{
string canonicalRequest = "GET" + "\n" +
canonicalURI + "\n" +
canonicalQueryString + "\n" +
canonicalHeaders + "\n" +
signedHeaders + "\n"
+ hashedPayload;
return canonicalRequest;
}
private static byte[] GenerateKey(string dateStamp, string region, string serviceName, string awsSecretKey)
{
byte[] kDate = HmacSha256(dateStamp, ToBytes("AWS4" + awsSecretKey));
byte[] kRegion = HmacSha256(region, kDate);
byte[] kService = HmacSha256(serviceName, kRegion);
return HmacSha256("aws4_request", kService);
}
private static string HexEncode(byte[] bytes)
{
return BitConverter.ToString(bytes).Replace("-", string.Empty).ToLowerInvariant();
}
private static byte[] ToBytes(string str)
{
return Encoding.UTF8.GetBytes(str.ToCharArray());
}
private static byte[] GenerateHash(byte[] bytes)
{
return SHA256.Create().ComputeHash(bytes);
}
private static byte[] HmacSha256(String data, byte[] key)
{
return new HMACSHA256(key).ComputeHash(ToBytes(data));
}
}
}
更新的规范请求字符串:
GET
/devcam/securehello
host:somerandomapigatewaystring.execute-api.us-west-2.amazonaws.com
x-amz-date:20161223T200041Z
host;x-amz-date
e3b0ooooyeaaac149afbf4c89somemorefake8888fvaluesa495991oooyea855
已更新要签名的字符串
AWS4-HMAC-SHA256
20161223T200041Z
20161223/us-west-2/execute-api/aws4_request
01b7ad17a58c442ed893ece5acb63a650b9781e32d23cb4d4114777d224deaf7
更新授权Header:
AWS4-HMAC-SHA256 Credential=ASPECIALSECRETEKEYKK/20161223/us-west-2/execute-api/aws4_request, SignedHeaders=host;x-amz-date, Signature=3e0immodifyingthesevaluesjusttobesafeoiuoiuoiuoiukjh8a30540609fe
您应该在 header 或查询参数 http://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp_use-resources.html 中传递 session 令牌,但您不需要将其包含在要签名的字符串中。
您是否尝试过测试这是否类似于邮递员? (https://www.getpostman.com/) 有可能,您发送了一些额外的 header。如果能把相关代码段分享出来,对调试会有帮助。
好的,我找到了让它工作的代码。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Text.RegularExpressions;
namespace bifrost
{
public class AwsSigningHelper
{
private const string Service = "execute-api";
public static readonly StringContent EmptyContent = new StringContent("", Encoding.UTF8, "application/json");
public const string Iso8601DateTimeFormat = "yyyyMMddTHHmmssZ";
public const string Iso8601DateFormat = "yyyyMMdd";
private static string GetHost(string url)
{
Regex reg = new Regex(@"https\:\/\/([a-z|0-9|A-Z]+\.execute-api\.[a-z|A-Z|0-9|-]+.amazonaws\.com).*");
Match match = reg.Match(url);
Console.WriteLine("host: " + match.Groups[1].Value);
return match.Groups[1].Value;
}
public static HttpRequestMessage GenerateAwsSignedGetRequest(string endpointUrl, string awsRegion, string accessKeyId, string secretAccessKey, string sessionToken)
{
return GenerateAwsSignedRequest(HttpMethod.Get, endpointUrl, EmptyContent,
awsRegion, accessKeyId, secretAccessKey, sessionToken);
}
public static HttpRequestMessage GenerateAwsSignedGetRequest(string endpointUrl, string awsRegion, string accessKeyId, string secretAccessKey)
{
return GenerateAwsSignedRequest(HttpMethod.Get, endpointUrl, EmptyContent,
awsRegion, accessKeyId, secretAccessKey, null);
}
public static HttpRequestMessage GenerateAwsSignedPostRequest(string endpointUrl, HttpContent content, string awsRegion, string accessKeyId, string secretAccessKey, string sessionToken)
{
return GenerateAwsSignedRequest(HttpMethod.Post, endpointUrl, content,
awsRegion, accessKeyId, secretAccessKey, sessionToken);
}
public static HttpRequestMessage GenerateAwsSignedPostRequest(string endpointUrl, HttpContent content, string awsRegion, string accessKeyId, string secretAccessKey)
{
return GenerateAwsSignedRequest(HttpMethod.Post, endpointUrl, content,
awsRegion, accessKeyId, secretAccessKey, null);
}
public static HttpRequestMessage GenerateAwsSignedPutRequest(string endpointUrl, HttpContent content, string awsRegion, string accessKeyId, string secretAccessKey, string sessionToken)
{
return GenerateAwsSignedRequest(HttpMethod.Put, endpointUrl, content,
awsRegion, accessKeyId, secretAccessKey, sessionToken);
}
public static HttpRequestMessage GenerateAwsSignedPutRequest(string endpointUrl, HttpContent content, string awsRegion, string accessKeyId, string secretAccessKey)
{
return GenerateAwsSignedRequest(HttpMethod.Put, endpointUrl, content,
awsRegion, accessKeyId, secretAccessKey, null);
}
private static string CreateAuthorizationHeader(string awsAccessKey, string signature, string signedHeaders, string dateStamp, string region, string serviceName)
{
return String.Format("AWS4-HMAC-SHA256 Credential={0}/{1}/{2}/{3}/aws4_request, SignedHeaders={4}, Signature={5}",
awsAccessKey, dateStamp, region, serviceName, signedHeaders, signature);
}
private static HttpRequestMessage GenerateAwsSignedRequest(HttpMethod method, string endpointUrl, HttpContent content, string awsRegion, string accessKeyId, string secretAccessKey, string sessionToken)
{
DateTime now = DateTime.UtcNow;
HttpRequestMessage httpRequest = new HttpRequestMessage(method, endpointUrl);
httpRequest.Headers.Add("host", GetHost(endpointUrl));
httpRequest.Headers.Add("x-amz-date", now.ToString(Iso8601DateTimeFormat));
httpRequest.Content = content;
List<string> signHeadersList = new List<string>
{
"content-type",
"host",
"x-amz-date",
};
if (!string.IsNullOrWhiteSpace(sessionToken))
{
signHeadersList.Add("x-amz-security-token");
httpRequest.Headers.Add("x-amz-security-token", sessionToken);
}
string[] signedHeaders = signHeadersList.ToArray();
string signedHeadersString = string.Join(";",
signedHeaders.Select(x => x.Trim().ToLowerInvariant()).OrderBy(x => x));
AwsV4SignatureCalculator signatureCalculator = new AwsV4SignatureCalculator(secretAccessKey, Service, awsRegion);
string signature = signatureCalculator.CalculateSignature(httpRequest, signedHeaders, now);
string authorization = CreateAuthorizationHeader(accessKeyId, signature,
signedHeadersString, now.ToString(Iso8601DateFormat), awsRegion, Service);
httpRequest.Headers.TryAddWithoutValidation("Authorization", authorization);
return httpRequest;
}
}
}
来自https://gist.github.com/yvanin/0bdf68c1139ad698519e:
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Net.Http;
using System.Security.Cryptography;
using System.Text;
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Extensions.Primitives;
namespace bifrost
{
public class AwsV4SignatureCalculator
{
public const string Iso8601DateTimeFormat = "yyyyMMddTHHmmssZ";
public const string Iso8601DateFormat = "yyyyMMdd";
private readonly string _awsSecretKey;
private readonly string _service;
private readonly string _region;
public AwsV4SignatureCalculator(string awsSecretKey, string service, string region = null)
{
_awsSecretKey = awsSecretKey;
_service = service;
_region = region ?? "us-east-1";
}
/// <summary>
/// Calculates request signature string using Signature Version 4.
/// http://docs.aws.amazon.com/general/latest/gr/sigv4_signing.html
/// </summary>
/// <param name="request">Request</param>
/// <param name="signedHeaders">Canonical headers that are a part of a signing process</param>
/// <param name="requestDate">Date and time when request takes place</param>
/// <returns>Signature</returns>
public string CalculateSignature(HttpRequestMessage request, string[] signedHeaders, DateTime requestDate)
{
signedHeaders = signedHeaders.Select(x => x.Trim().ToLowerInvariant()).OrderBy(x => x).ToArray();
var canonicalRequest = GetCanonicalRequest(request, signedHeaders);
var stringToSign = GetStringToSign(requestDate, canonicalRequest);
return GetSignature(requestDate, stringToSign);
}
// http://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html
private static string GetCanonicalRequest(HttpRequestMessage request, string[] signedHeaders)
{
var canonicalRequest = new StringBuilder();
canonicalRequest.AppendFormat("{0}\n", request.Method.Method);
canonicalRequest.AppendFormat("{0}\n", request.RequestUri.AbsolutePath);
canonicalRequest.AppendFormat("{0}\n", GetCanonicalQueryParameters(QueryHelpers.ParseQuery(request.RequestUri.Query)));
canonicalRequest.AppendFormat("{0}\n", GetCanonicalHeaders(request, signedHeaders));
canonicalRequest.AppendFormat("{0}\n", String.Join(";", signedHeaders));
canonicalRequest.Append(GetPayloadHash(request));
return canonicalRequest.ToString();
}
private static string GetCanonicalQueryParameters(Dictionary<string, StringValues> queryParameters)
{
StringBuilder canonicalQueryParameters = new StringBuilder();
foreach (KeyValuePair<string, StringValues> keyValuePair in queryParameters)
{
canonicalQueryParameters.AppendFormat("{0}={1}&", Utils.UrlEncode(keyValuePair.Key),
Utils.UrlEncode(keyValuePair.Value));
}
// remove trailing '&'
if (canonicalQueryParameters.Length > 0)
canonicalQueryParameters.Remove(canonicalQueryParameters.Length - 1, 1);
return canonicalQueryParameters.ToString();
}
private static string GetCanonicalHeaders(HttpRequestMessage request, IEnumerable<string> signedHeaders)
{
var headers = request.Headers.ToDictionary(x => x.Key.Trim().ToLowerInvariant(),
x => String.Join(" ", x.Value).Trim());
if (request.Content != null)
{
var contentHeaders = request.Content.Headers.ToDictionary(x => x.Key.Trim().ToLowerInvariant(),
x => String.Join(" ", x.Value).Trim());
foreach (var contentHeader in contentHeaders)
{
headers.Add(contentHeader.Key, contentHeader.Value);
}
}
var sortedHeaders = new SortedDictionary<string, string>(headers);
StringBuilder canonicalHeaders = new StringBuilder();
foreach (var header in sortedHeaders.Where(header => signedHeaders.Contains(header.Key)))
{
canonicalHeaders.AppendFormat("{0}:{1}\n", header.Key, header.Value);
}
return canonicalHeaders.ToString();
}
private static string GetPayloadHash(HttpRequestMessage request)
{
var payload = request.Content != null ? request.Content.ReadAsStringAsync().Result : "";
return Utils.ToHex(Utils.Hash(payload));
}
// http://docs.aws.amazon.com/general/latest/gr/sigv4-create-string-to-sign.html
private string GetStringToSign(DateTime requestDate, string canonicalRequest)
{
var dateStamp = requestDate.ToString(Iso8601DateFormat, CultureInfo.InvariantCulture);
var scope = string.Format("{0}/{1}/{2}/{3}", dateStamp, _region, _service, "aws4_request");
var stringToSign = new StringBuilder();
stringToSign.AppendFormat("AWS4-HMAC-SHA256\n{0}\n{1}\n",
requestDate.ToString(Iso8601DateTimeFormat, CultureInfo.InvariantCulture),
scope);
stringToSign.Append(Utils.ToHex(Utils.Hash(canonicalRequest)));
return stringToSign.ToString();
}
// http://docs.aws.amazon.com/general/latest/gr/sigv4-calculate-signature.html
private string GetSignature(DateTime requestDate, string stringToSign)
{
var kSigning = GetSigningKey(requestDate);
return Utils.ToHex(Utils.GetKeyedHash(kSigning, stringToSign));
}
private byte[] GetSigningKey(DateTime requestDate)
{
var dateStamp = requestDate.ToString(Iso8601DateFormat, CultureInfo.InvariantCulture);
var kDate = Utils.GetKeyedHash("AWS4" + _awsSecretKey, dateStamp);
var kRegion = Utils.GetKeyedHash(kDate, _region);
var kService = Utils.GetKeyedHash(kRegion, _service);
return Utils.GetKeyedHash(kService, "aws4_request");
}
private static class Utils
{
private const string ValidUrlCharacters =
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~";
public static string UrlEncode(string data)
{
StringBuilder encoded = new StringBuilder();
foreach (char symbol in Encoding.UTF8.GetBytes(data))
{
if (ValidUrlCharacters.IndexOf(symbol) != -1)
{
encoded.Append(symbol);
}
else
{
encoded.Append("%").Append(string.Format(CultureInfo.InvariantCulture, "{0:X2}", (int)symbol));
}
}
return encoded.ToString();
}
public static byte[] Hash(string value)
{
using (var sha256Algorithm = SHA256.Create())
{
byte[] hash = sha256Algorithm.ComputeHash(Encoding.UTF8.GetBytes(value));
return hash;
}
}
public static byte[] GetKeyedHash(string key, string value)
{
return GetKeyedHash(Encoding.UTF8.GetBytes(key), value);
}
public static byte[] GetKeyedHash(byte[] key, string value)
{
KeyedHashAlgorithm mac = new HMACSHA256(key);
mac.Initialize();
return mac.ComputeHash(Encoding.UTF8.GetBytes(value));
}
public static string ToHex(byte[] data)
{
StringBuilder sb = new StringBuilder();
for (int i = 0; i < data.Length; i++)
{
sb.Append(data[i].ToString("x2", CultureInfo.InvariantCulture));
}
return sb.ToString();
}
}
}
}
用法示例:
HttpRequestMessage httpRequestPost =
AwsSigningHelper.GenerateAwsSignedPostRequest(someapigatewayurl,
// AwsSigningHelper.EmptyContent,
// new StringContent("{ \"test\" : \"test\" }", Encoding.UTF8, "application/json"),
new StringContent("text!", Encoding.UTF8, "text/plain"),
EnvironmentVariables.Region, EnvironmentVariables.AccessKeyId, EnvironmentVariables.SecretAccessKey,
EnvironmentVariables.SessionToken);
var responsePost = httpClient.SendAsync(httpRequestPost).Result;
这是我遇到的错误....
The request signature we calculated does not match the signature you provided. Check your AWS Secret Access Key and signing method. Consult the service documentation for details.
The Canonical String for this request should have been
'GET
/devcam/securehello
host:*.execute-api.us-west-2.amazonaws.com
x-amz-date:20161223T170922Z
host;x-amz-date
e3b0c44098098s098df098s...8f0s98df098sd09f8s0d98f095'
The String-to-Sign should have been
'AWS4-HMAC-SHA256
20161223T170922Z
20161223/us-west-2/execute-api/aws4_request
828177fca39making23409...9840928304980238402983282d'
奇怪的是我将它与我的 Canonical String 和 String to Sign 进行比较,它们是相同的...我还需要检查什么?
我正在尝试使用临时 aws lambda 凭据调用它。我不确定我应该如何使用 Session 令牌,但现在它被添加为 header (我试过将它包含在签名的 header 中并且不包括它——两者都不起作用)"X-Amz-Security-Token".
编辑:
这是我用来发送请求的来自 .NET 核心的 JSON 序列化 HttpMessageRequest object。
{
"Version": {
"Major": 1,
"Minor": 1,
"Build": -1,
"Revision": -1,
"MajorRevision": -1,
"MinorRevision": -1
},
"Content": null,
"Method": {
"Method": "GET"
},
"RequestUri": "https:\/\/somerandomapigatewaystring.execute-api.us-west-2.amazonaws.com\/devcam\/securehello",
"Headers": [
{
"Key": "x-amz-security-token",
"Value": [
"FQoDYXdzEKr\/\/\/\/\/\/\/\/\/\/wEaDPuFLZoHsomefmorefakevaluesin5here0+36yIwO1kh8sdkeuf98eek3kj4934898erjthisisafakevaluehahahahahahahahaz8FE7jKvENO1IdshzqjDFWhpnfLESAS4B404dBW\/VAkH1jJDvpiFylP5qiZk6q+S\/eJHDdLUoVAXDqkh0w2wkFdXNpsMsfr8eLdzXP8jT\/ZDA+OxG2qzxd1UaotDl\/\/NSzWjNyOYGX3pEoNk9LtefKBGKbgTvH5lrguydfN8JFb4mGjtP8wRY7tjHHq8jtLQhJMRiub3eGnR\/vxwNQk8EJ5usJxB1UF2Kl0p5LqSohViivqfXCBQ=="
]
},
{
"Key": "host",
"Value": [
"somerandomapigatewaystring.execute-api.us-west-2.amazonaws.com"
]
},
{
"Key": "x-amz-date",
"Value": [
"20161223T200041Z"
]
},
{
"Key": "Authorization",
"Value": [
"AWS4-HMAC-SHA256 Credential=********************\/20161223\/us-west-2\/execute-api\/aws4_request, SignedHeaders=host;x-amz-date, Signature=3e0d3***********************************************************"
]
}
],
"Properties": {
}
}
这是我的代码 -- 对于混乱,我深表歉意。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
using Amazon.CloudFormation;
using Amazon.IdentityManagement;
using Amazon.IdentityManagement.Model;
using Amazon.Lambda.Core;
using Amazon.Runtime.Internal;
using Amazon.Runtime.Internal.Auth;
using Amazon.SecurityToken;
using Amazon.SecurityToken.Model;
using bifrost;
using Newtonsoft.Json;
namespace Bifrost
{
public class Handler
{
private static HttpClient httpClient = new HttpClient(); //yes we want this to be static https://aspnetmonsters.com/2016/08/2016-08-27-httpclientwrong/
private static string Service = "execute-api";
// private static string Service = "apigateway";
private static string Algorithm = "AWS4-HMAC-SHA256";
public const string Iso8601DateTimeFormat = "yyyyMMddTHHmmssZ";
public const string Iso8601DateFormat = "yyyyMMdd";
public Response Hello(Request request, ILambdaContext context)
{
try
{
var now = DateTime.Now;
var signedHeaders = new string[]
{
// "content-type",
"host", //http://docs.aws.amazon.com/general/latest/gr/rande.html#apigateway_region
"x-amz-date",
// "x-amz-security-token",
// "Authorization",
// "Credential"
};
//X-Amz-Security-Token
HttpRequestMessage httpRequest = new HttpRequestMessage(HttpMethod.Get, "https://somerandomapigatewaystring.execute-api.us-west-2.amazonaws.com/devcam/securehello");
httpRequest.Headers.Add("x-amz-security-token", EnvironmentVariables.SessionToken);
// httpRequest.Headers.Add("host", "apigateway.us-west-2.amazonaws.com");
httpRequest.Headers.Add("host", "somerandomapigatewaystring.execute-api.us-west-2.amazonaws.com");
httpRequest.Headers.Add("x-amz-date", now.ToString(Iso8601DateTimeFormat));
// httpRequest.Content.Headers.ContentType = MediaTypeHeaderValue.Parse("application/json");
// httpRequest.Headers.Add("X-Amz-Security-Token", EnvironmentVariables.SessionToken);
string credentialScope = String.Format("{0}/{1}/{2}/aws4_request", now.ToString(Iso8601DateFormat),
EnvironmentVariables.Region, Service);
string credentialString = EnvironmentVariables.AccessKeyId + "/" + credentialScope;
string signedHeadersString = string.Join(";",
signedHeaders.Select(x => x.Trim().ToLowerInvariant()).OrderBy(x => x));
// AwsV4SignatureCalculator signatureCalculator = new AwsV4SignatureCalculator(EnvironmentVariables.SecretKey, Service, "us-west-2");
// string signature = signatureCalculator.CalculateSignature(httpRequest, signedHeaders, now);
string signature = getSignature(EnvironmentVariables.AccessKeyId, Service, "us-west-2", httpRequest, signedHeaders, now);
// task 4: add signing info to request
string authorization = CreateAuthorizationGET(signature, signedHeadersString, credentialScope);
Console.WriteLine("authorization:");
Console.WriteLine(authorization);
// The format of value 'AWS4-HMAC-SHA256 Credential=/20161222/us-west-2/execute-api/aws4_request, SignedHeaders=host;x-amz-date;x-amz-security-token, Signature=.
// httpRequest.Headers.Add("Authorization", authorization);
httpRequest.Headers.TryAddWithoutValidation("Authorization", authorization);
// httpRequest.Headers.TryAddWithoutValidation("Authorization", authorization + ", X-Amz-Security-Token=" + EnvironmentVariables.SessionToken);
// httpRequest.Version = Version./
var response = httpClient.SendAsync(httpRequest);
return new Response
{
Body =
"Go Serverless v1.0! Your function executed successfully!\r\n" +
JsonConvert.SerializeObject(request)
+ "\r\n\r\n" + JsonConvert.SerializeObject(context) + "\r\n\r\n" +
JsonConvert.SerializeObject(Environment.GetEnvironmentVariables()) + "\r\n\r\n" +
JsonConvert.SerializeObject(httpRequest) + "\r\n\r\n" +
JsonConvert.SerializeObject(response.Result) + "\r\n\r\n" +
// "",
response.Result.Content.ReadAsStringAsync().Result,
StatusCode = 200,
Headers = new Dictionary<string, string>()
};
}
catch (Exception e)
{
return new Response
{
StatusCode = 200,
Body = e.Message + "\r\n\r\n" + e.StackTrace +
"\r\n\r\n" + e.InnerException.StackTrace,
Headers = new Dictionary<string, string>()
};
}
}
private string getSignature(string accessKey, string service, string region, HttpRequestMessage httpRequest, string[] signedHeaders, DateTime now)
{
// httpRequest.Content = new StringContent("", Encoding.UTF8,
// "application/json");
var signedHeadersDict = createSignedHeadersDictionary(signedHeaders, httpRequest);
string hashedPayload = AWSAuthenticationHelper.HashAndHexEncodeString("");
string canonicalHeaders = string.Join("\n", signedHeadersDict.Select(x => x.Key.ToLowerInvariant() + ":" + x.Value.Trim())) + "\n";
string signedHeadersString = string.Join(";", signedHeaders);
// httpRequest.Headers.Add("X-Amz-SignedHeaders", signedHeadersString);
string canonicalRequest = AWSAuthenticationHelper.CreateCanconicalRequestGET(hashedPayload, signedHeadersString, canonicalHeaders,
httpRequest.RequestUri.AbsolutePath,
httpRequest.RequestUri.Query);
Console.WriteLine("Canonical String:");
Console.WriteLine(canonicalRequest);
string hashedCanonicalRequest = AWSAuthenticationHelper.HashAndHexEncodeString(canonicalRequest);
string dateStamp = now.ToString(Iso8601DateFormat);
string dateTimeStamp = now.ToString(Iso8601DateTimeFormat);
// httpRequest.Headers.Add("X-Amz-Date", now.ToString(Iso8601DateTimeFormat));
string credentialScope = String.Format("{0}/{1}/{2}/aws4_request", dateStamp, region, service);
// httpRequest.Headers.Add("X-Amz-Credential", credentialScope);
string stringToSign = CreateStringToSign(hashedCanonicalRequest, dateTimeStamp, credentialScope);
Console.WriteLine("stringToSign:");
Console.WriteLine(stringToSign);
// task 3: calculate signature
string signature = AWSAuthenticationHelper.CreateSignature(stringToSign, dateStamp, region, service, accessKey);
return signature;
}
private string CreateAuthorizationGET(string signature, string signedHeaders, string credentialScope)
{
return String.Format("{0} Credential={1}/{2}, SignedHeaders={3}, Signature={4}", Algorithm, EnvironmentVariables.AccessKeyId, credentialScope, signedHeaders, signature);
}
private string CreateStringToSign(string hashedRequest, string requestDate, string credentialScope)
{
string stringToSign = string.Format("{0}\n{1}\n{2}\n{3}", Algorithm, requestDate, credentialScope, hashedRequest);
return stringToSign;
}
private SortedDictionary<string, string> createSignedHeadersDictionary(string[] signedHeaders, HttpRequestMessage httpRequest)
{
SortedDictionary<string, string> ret = new SortedDictionary<string, string>();
List<string> signedHeadersList = signedHeaders.Select(x => x.ToLowerInvariant().Trim()).OrderBy(x => x).ToList();
foreach (KeyValuePair<string, IEnumerable<string>> header in httpRequest.Headers)
{
if (signedHeadersList.Contains(header.Key.ToLowerInvariant().Trim()))
{
ret.Add(header.Key, String.Join(",", header.Value)); // TODO: is tostring safe here?
}
}
if (httpRequest.Content != null && httpRequest.Content.Headers != null)
{
foreach (var contentHeader in httpRequest.Content.Headers)
{
if (signedHeaders.Contains(contentHeader.Key.ToLowerInvariant().Trim()))
{
ret.Add(contentHeader.Key, string.Join(",", contentHeader.Value));
}
}
}
// else if (signedHeadersList.Contains("content-type"))
// {
// throw new Exception("content-type header set but no Content found");
// }
return ret;
}
public Response SecureHello(Request request, ILambdaContext context)
{
return new Response
{
Body = "securely called!\r\n" + JsonConvert.SerializeObject(request)
+ "\r\n\r\n" + JsonConvert.SerializeObject(context) + "\r\n\r\n" +
JsonConvert.SerializeObject(Environment.GetEnvironmentVariables()),
StatusCode = 200,
Headers = new Dictionary<string, string>()
};
}
}
public class Response : BaseResponse
{
}
public class Request : BaseRequest
{
}
}
还有更多代码...
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
namespace bifrost
{
public class AWSAuthenticationHelper
{
public static string HashAndHexEncodeString(string str)
{
return HexEncode(GenerateHash(ToBytes(str)));
}
public static string CreateSignature(string stringToSign, string dateStamp, string region, string serviceName, string awsKey)
{
byte[] key = GenerateKey(dateStamp, region, serviceName, awsKey);
return HexEncode(HmacSha256(stringToSign, key));
}
public static string CreateCanconicalRequestPOST(string hashedPayload, string signedHeaders, string canonicalHeaders, string canonicalURI)
{
string canonicalRequest = "POST" + "\n" + canonicalURI + "\n" + "" + "\n" + canonicalHeaders + "\n" +
signedHeaders + "\n" + hashedPayload;
return canonicalRequest;
}
public static string CreateCanconicalRequestGET(string hashedPayload, string signedHeaders, string canonicalHeaders, string canonicalURI, string canonicalQueryString)
{
string canonicalRequest = "GET" + "\n" +
canonicalURI + "\n" +
canonicalQueryString + "\n" +
canonicalHeaders + "\n" +
signedHeaders + "\n"
+ hashedPayload;
return canonicalRequest;
}
private static byte[] GenerateKey(string dateStamp, string region, string serviceName, string awsSecretKey)
{
byte[] kDate = HmacSha256(dateStamp, ToBytes("AWS4" + awsSecretKey));
byte[] kRegion = HmacSha256(region, kDate);
byte[] kService = HmacSha256(serviceName, kRegion);
return HmacSha256("aws4_request", kService);
}
private static string HexEncode(byte[] bytes)
{
return BitConverter.ToString(bytes).Replace("-", string.Empty).ToLowerInvariant();
}
private static byte[] ToBytes(string str)
{
return Encoding.UTF8.GetBytes(str.ToCharArray());
}
private static byte[] GenerateHash(byte[] bytes)
{
return SHA256.Create().ComputeHash(bytes);
}
private static byte[] HmacSha256(String data, byte[] key)
{
return new HMACSHA256(key).ComputeHash(ToBytes(data));
}
}
}
更新的规范请求字符串:
GET
/devcam/securehello
host:somerandomapigatewaystring.execute-api.us-west-2.amazonaws.com
x-amz-date:20161223T200041Z
host;x-amz-date
e3b0ooooyeaaac149afbf4c89somemorefake8888fvaluesa495991oooyea855
已更新要签名的字符串
AWS4-HMAC-SHA256
20161223T200041Z
20161223/us-west-2/execute-api/aws4_request
01b7ad17a58c442ed893ece5acb63a650b9781e32d23cb4d4114777d224deaf7
更新授权Header:
AWS4-HMAC-SHA256 Credential=ASPECIALSECRETEKEYKK/20161223/us-west-2/execute-api/aws4_request, SignedHeaders=host;x-amz-date, Signature=3e0immodifyingthesevaluesjusttobesafeoiuoiuoiuoiukjh8a30540609fe
您应该在 header 或查询参数 http://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp_use-resources.html 中传递 session 令牌,但您不需要将其包含在要签名的字符串中。
您是否尝试过测试这是否类似于邮递员? (https://www.getpostman.com/) 有可能,您发送了一些额外的 header。如果能把相关代码段分享出来,对调试会有帮助。
好的,我找到了让它工作的代码。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Text.RegularExpressions;
namespace bifrost
{
public class AwsSigningHelper
{
private const string Service = "execute-api";
public static readonly StringContent EmptyContent = new StringContent("", Encoding.UTF8, "application/json");
public const string Iso8601DateTimeFormat = "yyyyMMddTHHmmssZ";
public const string Iso8601DateFormat = "yyyyMMdd";
private static string GetHost(string url)
{
Regex reg = new Regex(@"https\:\/\/([a-z|0-9|A-Z]+\.execute-api\.[a-z|A-Z|0-9|-]+.amazonaws\.com).*");
Match match = reg.Match(url);
Console.WriteLine("host: " + match.Groups[1].Value);
return match.Groups[1].Value;
}
public static HttpRequestMessage GenerateAwsSignedGetRequest(string endpointUrl, string awsRegion, string accessKeyId, string secretAccessKey, string sessionToken)
{
return GenerateAwsSignedRequest(HttpMethod.Get, endpointUrl, EmptyContent,
awsRegion, accessKeyId, secretAccessKey, sessionToken);
}
public static HttpRequestMessage GenerateAwsSignedGetRequest(string endpointUrl, string awsRegion, string accessKeyId, string secretAccessKey)
{
return GenerateAwsSignedRequest(HttpMethod.Get, endpointUrl, EmptyContent,
awsRegion, accessKeyId, secretAccessKey, null);
}
public static HttpRequestMessage GenerateAwsSignedPostRequest(string endpointUrl, HttpContent content, string awsRegion, string accessKeyId, string secretAccessKey, string sessionToken)
{
return GenerateAwsSignedRequest(HttpMethod.Post, endpointUrl, content,
awsRegion, accessKeyId, secretAccessKey, sessionToken);
}
public static HttpRequestMessage GenerateAwsSignedPostRequest(string endpointUrl, HttpContent content, string awsRegion, string accessKeyId, string secretAccessKey)
{
return GenerateAwsSignedRequest(HttpMethod.Post, endpointUrl, content,
awsRegion, accessKeyId, secretAccessKey, null);
}
public static HttpRequestMessage GenerateAwsSignedPutRequest(string endpointUrl, HttpContent content, string awsRegion, string accessKeyId, string secretAccessKey, string sessionToken)
{
return GenerateAwsSignedRequest(HttpMethod.Put, endpointUrl, content,
awsRegion, accessKeyId, secretAccessKey, sessionToken);
}
public static HttpRequestMessage GenerateAwsSignedPutRequest(string endpointUrl, HttpContent content, string awsRegion, string accessKeyId, string secretAccessKey)
{
return GenerateAwsSignedRequest(HttpMethod.Put, endpointUrl, content,
awsRegion, accessKeyId, secretAccessKey, null);
}
private static string CreateAuthorizationHeader(string awsAccessKey, string signature, string signedHeaders, string dateStamp, string region, string serviceName)
{
return String.Format("AWS4-HMAC-SHA256 Credential={0}/{1}/{2}/{3}/aws4_request, SignedHeaders={4}, Signature={5}",
awsAccessKey, dateStamp, region, serviceName, signedHeaders, signature);
}
private static HttpRequestMessage GenerateAwsSignedRequest(HttpMethod method, string endpointUrl, HttpContent content, string awsRegion, string accessKeyId, string secretAccessKey, string sessionToken)
{
DateTime now = DateTime.UtcNow;
HttpRequestMessage httpRequest = new HttpRequestMessage(method, endpointUrl);
httpRequest.Headers.Add("host", GetHost(endpointUrl));
httpRequest.Headers.Add("x-amz-date", now.ToString(Iso8601DateTimeFormat));
httpRequest.Content = content;
List<string> signHeadersList = new List<string>
{
"content-type",
"host",
"x-amz-date",
};
if (!string.IsNullOrWhiteSpace(sessionToken))
{
signHeadersList.Add("x-amz-security-token");
httpRequest.Headers.Add("x-amz-security-token", sessionToken);
}
string[] signedHeaders = signHeadersList.ToArray();
string signedHeadersString = string.Join(";",
signedHeaders.Select(x => x.Trim().ToLowerInvariant()).OrderBy(x => x));
AwsV4SignatureCalculator signatureCalculator = new AwsV4SignatureCalculator(secretAccessKey, Service, awsRegion);
string signature = signatureCalculator.CalculateSignature(httpRequest, signedHeaders, now);
string authorization = CreateAuthorizationHeader(accessKeyId, signature,
signedHeadersString, now.ToString(Iso8601DateFormat), awsRegion, Service);
httpRequest.Headers.TryAddWithoutValidation("Authorization", authorization);
return httpRequest;
}
}
}
来自https://gist.github.com/yvanin/0bdf68c1139ad698519e:
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Net.Http;
using System.Security.Cryptography;
using System.Text;
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Extensions.Primitives;
namespace bifrost
{
public class AwsV4SignatureCalculator
{
public const string Iso8601DateTimeFormat = "yyyyMMddTHHmmssZ";
public const string Iso8601DateFormat = "yyyyMMdd";
private readonly string _awsSecretKey;
private readonly string _service;
private readonly string _region;
public AwsV4SignatureCalculator(string awsSecretKey, string service, string region = null)
{
_awsSecretKey = awsSecretKey;
_service = service;
_region = region ?? "us-east-1";
}
/// <summary>
/// Calculates request signature string using Signature Version 4.
/// http://docs.aws.amazon.com/general/latest/gr/sigv4_signing.html
/// </summary>
/// <param name="request">Request</param>
/// <param name="signedHeaders">Canonical headers that are a part of a signing process</param>
/// <param name="requestDate">Date and time when request takes place</param>
/// <returns>Signature</returns>
public string CalculateSignature(HttpRequestMessage request, string[] signedHeaders, DateTime requestDate)
{
signedHeaders = signedHeaders.Select(x => x.Trim().ToLowerInvariant()).OrderBy(x => x).ToArray();
var canonicalRequest = GetCanonicalRequest(request, signedHeaders);
var stringToSign = GetStringToSign(requestDate, canonicalRequest);
return GetSignature(requestDate, stringToSign);
}
// http://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html
private static string GetCanonicalRequest(HttpRequestMessage request, string[] signedHeaders)
{
var canonicalRequest = new StringBuilder();
canonicalRequest.AppendFormat("{0}\n", request.Method.Method);
canonicalRequest.AppendFormat("{0}\n", request.RequestUri.AbsolutePath);
canonicalRequest.AppendFormat("{0}\n", GetCanonicalQueryParameters(QueryHelpers.ParseQuery(request.RequestUri.Query)));
canonicalRequest.AppendFormat("{0}\n", GetCanonicalHeaders(request, signedHeaders));
canonicalRequest.AppendFormat("{0}\n", String.Join(";", signedHeaders));
canonicalRequest.Append(GetPayloadHash(request));
return canonicalRequest.ToString();
}
private static string GetCanonicalQueryParameters(Dictionary<string, StringValues> queryParameters)
{
StringBuilder canonicalQueryParameters = new StringBuilder();
foreach (KeyValuePair<string, StringValues> keyValuePair in queryParameters)
{
canonicalQueryParameters.AppendFormat("{0}={1}&", Utils.UrlEncode(keyValuePair.Key),
Utils.UrlEncode(keyValuePair.Value));
}
// remove trailing '&'
if (canonicalQueryParameters.Length > 0)
canonicalQueryParameters.Remove(canonicalQueryParameters.Length - 1, 1);
return canonicalQueryParameters.ToString();
}
private static string GetCanonicalHeaders(HttpRequestMessage request, IEnumerable<string> signedHeaders)
{
var headers = request.Headers.ToDictionary(x => x.Key.Trim().ToLowerInvariant(),
x => String.Join(" ", x.Value).Trim());
if (request.Content != null)
{
var contentHeaders = request.Content.Headers.ToDictionary(x => x.Key.Trim().ToLowerInvariant(),
x => String.Join(" ", x.Value).Trim());
foreach (var contentHeader in contentHeaders)
{
headers.Add(contentHeader.Key, contentHeader.Value);
}
}
var sortedHeaders = new SortedDictionary<string, string>(headers);
StringBuilder canonicalHeaders = new StringBuilder();
foreach (var header in sortedHeaders.Where(header => signedHeaders.Contains(header.Key)))
{
canonicalHeaders.AppendFormat("{0}:{1}\n", header.Key, header.Value);
}
return canonicalHeaders.ToString();
}
private static string GetPayloadHash(HttpRequestMessage request)
{
var payload = request.Content != null ? request.Content.ReadAsStringAsync().Result : "";
return Utils.ToHex(Utils.Hash(payload));
}
// http://docs.aws.amazon.com/general/latest/gr/sigv4-create-string-to-sign.html
private string GetStringToSign(DateTime requestDate, string canonicalRequest)
{
var dateStamp = requestDate.ToString(Iso8601DateFormat, CultureInfo.InvariantCulture);
var scope = string.Format("{0}/{1}/{2}/{3}", dateStamp, _region, _service, "aws4_request");
var stringToSign = new StringBuilder();
stringToSign.AppendFormat("AWS4-HMAC-SHA256\n{0}\n{1}\n",
requestDate.ToString(Iso8601DateTimeFormat, CultureInfo.InvariantCulture),
scope);
stringToSign.Append(Utils.ToHex(Utils.Hash(canonicalRequest)));
return stringToSign.ToString();
}
// http://docs.aws.amazon.com/general/latest/gr/sigv4-calculate-signature.html
private string GetSignature(DateTime requestDate, string stringToSign)
{
var kSigning = GetSigningKey(requestDate);
return Utils.ToHex(Utils.GetKeyedHash(kSigning, stringToSign));
}
private byte[] GetSigningKey(DateTime requestDate)
{
var dateStamp = requestDate.ToString(Iso8601DateFormat, CultureInfo.InvariantCulture);
var kDate = Utils.GetKeyedHash("AWS4" + _awsSecretKey, dateStamp);
var kRegion = Utils.GetKeyedHash(kDate, _region);
var kService = Utils.GetKeyedHash(kRegion, _service);
return Utils.GetKeyedHash(kService, "aws4_request");
}
private static class Utils
{
private const string ValidUrlCharacters =
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.~";
public static string UrlEncode(string data)
{
StringBuilder encoded = new StringBuilder();
foreach (char symbol in Encoding.UTF8.GetBytes(data))
{
if (ValidUrlCharacters.IndexOf(symbol) != -1)
{
encoded.Append(symbol);
}
else
{
encoded.Append("%").Append(string.Format(CultureInfo.InvariantCulture, "{0:X2}", (int)symbol));
}
}
return encoded.ToString();
}
public static byte[] Hash(string value)
{
using (var sha256Algorithm = SHA256.Create())
{
byte[] hash = sha256Algorithm.ComputeHash(Encoding.UTF8.GetBytes(value));
return hash;
}
}
public static byte[] GetKeyedHash(string key, string value)
{
return GetKeyedHash(Encoding.UTF8.GetBytes(key), value);
}
public static byte[] GetKeyedHash(byte[] key, string value)
{
KeyedHashAlgorithm mac = new HMACSHA256(key);
mac.Initialize();
return mac.ComputeHash(Encoding.UTF8.GetBytes(value));
}
public static string ToHex(byte[] data)
{
StringBuilder sb = new StringBuilder();
for (int i = 0; i < data.Length; i++)
{
sb.Append(data[i].ToString("x2", CultureInfo.InvariantCulture));
}
return sb.ToString();
}
}
}
}
用法示例:
HttpRequestMessage httpRequestPost =
AwsSigningHelper.GenerateAwsSignedPostRequest(someapigatewayurl,
// AwsSigningHelper.EmptyContent,
// new StringContent("{ \"test\" : \"test\" }", Encoding.UTF8, "application/json"),
new StringContent("text!", Encoding.UTF8, "text/plain"),
EnvironmentVariables.Region, EnvironmentVariables.AccessKeyId, EnvironmentVariables.SecretAccessKey,
EnvironmentVariables.SessionToken);
var responsePost = httpClient.SendAsync(httpRequestPost).Result;