-
Notifications
You must be signed in to change notification settings - Fork 756
/
Copy pathAWSSignerHelper.cs
267 lines (231 loc) · 12.2 KB
/
AWSSignerHelper.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using RestSharp;
using System.Text.RegularExpressions;
using System.Globalization;
using Amazon.SecurityToken.Model;
namespace Amazon.SellingPartnerAPIAA
{
public class AWSSignerHelper
{
public const string ISO8601BasicDateTimeFormat = "yyyyMMddTHHmmssZ";
public const string ISO8601BasicDateFormat = "yyyyMMdd";
public const string XAmzDateHeaderName = "X-Amz-Date";
public const string AuthorizationHeaderName = "Authorization";
public const string CredentialSubHeaderName = "Credential";
public const string SignatureSubHeaderName = "Signature";
public const string SignedHeadersSubHeaderName = "SignedHeaders";
public const string HostHeaderName = "host";
public const string SecurityTokenHeaderName = "X-Amz-Security-Token";
public const string Scheme = "AWS4";
public const string Algorithm = "HMAC-SHA256";
public const string TerminationString = "aws4_request";
public const string ServiceName = "execute-api";
public const string Slash = "/";
private readonly static Regex CompressWhitespaceRegex = new Regex("\\s+");
public virtual IDateHelper DateHelper { get; set; }
public AWSSignerHelper()
{
DateHelper = new SigningDateHelper();
}
/// <summary>
/// Returns URI encoded version of absolute path
/// </summary>
/// <param name="resource">Resource path(absolute path) from the request</param>
/// <returns>URI encoded version of absolute path</returns>
public virtual string ExtractCanonicalURIParameters(string resource)
{
string canonicalUri = string.Empty;
if (string.IsNullOrEmpty(resource))
{
canonicalUri = Slash;
}
else
{
if (!resource.StartsWith(Slash))
{
canonicalUri = Slash;
}
//Split path at / into segments
IEnumerable<string> encodedSegments = resource.Split(new char[] { '/' }, StringSplitOptions.None);
// Encode twice
encodedSegments = encodedSegments.Select(segment => Utils.UrlEncode(segment));
encodedSegments = encodedSegments.Select(segment => Utils.UrlEncode(segment));
canonicalUri += string.Join(Slash, encodedSegments.ToArray());
}
return canonicalUri;
}
/// <summary>
/// Returns query parameters in canonical order with URL encoding
/// </summary>
/// <param name="request">RestRequest</param>
/// <returns>Query parameters in canonical order with URL encoding</returns>
public virtual string ExtractCanonicalQueryString(IRestRequest request)
{
IDictionary<string, string> queryParameters = request.Parameters
.Where(parameter => ParameterType.QueryString.Equals(parameter.Type))
.ToDictionary(header => header.Name.Trim().ToString(), header => header.Value.ToString());
SortedDictionary<string, string> sortedqueryParameters = new SortedDictionary<string, string>(queryParameters);
StringBuilder canonicalQueryString = new StringBuilder();
foreach (var key in sortedqueryParameters.Keys)
{
if (canonicalQueryString.Length > 0)
{
canonicalQueryString.Append("&");
}
canonicalQueryString.AppendFormat("{0}={1}",
Utils.UrlEncode(key),
Utils.UrlEncode(sortedqueryParameters[key]));
}
return canonicalQueryString.ToString();
}
/// <summary>
/// Returns Http headers in canonical order with all header names to lowercase
/// </summary>
/// <param name="request">RestRequest</param>
/// <returns>Returns Http headers in canonical order</returns>
public virtual string ExtractCanonicalHeaders(IRestRequest request)
{
IDictionary<string, string> headers = request.Parameters
.Where(parameter => ParameterType.HttpHeader.Equals(parameter.Type))
.ToDictionary(header => header.Name.Trim().ToLowerInvariant(), header => header.Value.ToString());
SortedDictionary<string, string> sortedHeaders = new SortedDictionary<string, string>(headers);
StringBuilder headerString = new StringBuilder();
foreach (string headerName in sortedHeaders.Keys)
{
headerString.AppendFormat("{0}:{1}\n",
headerName,
CompressWhitespaceRegex.Replace(sortedHeaders[headerName].Trim(), " "));
}
return headerString.ToString();
}
/// <summary>
/// Returns list(as string) of Http headers in canonical order
/// </summary>
/// <param name="request">RestRequest</param>
/// <returns>List of Http headers in canonical order</returns>
public virtual string ExtractSignedHeaders(IRestRequest request)
{
List<string> rawHeaders = request.Parameters.Where(parameter => ParameterType.HttpHeader.Equals(parameter.Type))
.Select(header => header.Name.Trim().ToLowerInvariant())
.ToList();
rawHeaders.Sort(StringComparer.OrdinalIgnoreCase);
return string.Join(";", rawHeaders);
}
/// <summary>
/// Returns hexadecimal hashed value(using SHA256) of payload in the body of request
/// </summary>
/// <param name="request">RestRequest</param>
/// <returns>Hexadecimal hashed value of payload in the body of request</returns>
public virtual string HashRequestBody(IRestRequest request)
{
Parameter body = request.Parameters.FirstOrDefault(parameter => ParameterType.RequestBody.Equals(parameter.Type));
string value = body != null ? body.Value.ToString() : string.Empty;
return Utils.ToHex(Utils.Hash(value));
}
/// <summary>
/// Builds the string for signing using signing date, hashed canonical request and region
/// </summary>
/// <param name="signingDate">Signing Date</param>
/// <param name="hashedCanonicalRequest">Hashed Canonical Request</param>
/// <param name="region">Region</param>
/// <returns>String to be used for signing</returns>
public virtual string BuildStringToSign(DateTime signingDate, string hashedCanonicalRequest, string region)
{
string scope = BuildScope(signingDate, region);
string stringToSign = string.Format(CultureInfo.InvariantCulture, "{0}-{1}\n{2}\n{3}\n{4}",
Scheme,
Algorithm,
signingDate.ToString(ISO8601BasicDateTimeFormat, CultureInfo.InvariantCulture),
scope,
hashedCanonicalRequest);
return stringToSign;
}
/// <summary>
/// Sets AWS4 mandated 'x-amz-date' and 'host' headers, returning the date/time that will
/// be used throughout the signing process.
/// </summary>
/// <param name="restRequest">RestRequest</param>
/// <param name="host">Request endpoint</param>
/// <returns>Date and time used for x-amz-date, in UTC</returns>
public virtual DateTime SetDateAndHostHeaders(IRestRequest restRequest, string host)
{
restRequest.Parameters.RemoveAll(parameter => ParameterType.HttpHeader.Equals(parameter.Type)
&& parameter.Name == XAmzDateHeaderName);
restRequest.Parameters.RemoveAll(parameter => ParameterType.HttpHeader.Equals(parameter.Type)
&& parameter.Name == HostHeaderName);
DateTime signingDate = DateHelper.GetUtcNow();
restRequest.AddHeader(XAmzDateHeaderName, signingDate.ToString(ISO8601BasicDateTimeFormat, CultureInfo.InvariantCulture));
restRequest.AddHeader(HostHeaderName, host);
return signingDate;
}
/// <summary>
/// Sets AWS4 'X-Amz-Security-Token' header, used to pass the STS Token to
/// be used throughout the signing process.
/// </summary>
/// <param name="restRequest">RestRequest</param>
/// <param name="sessionToken">STS Session Token</param>
public void SetSessionTokenHeader(IRestRequest restRequest, String sessionToken)
{
restRequest.Parameters.RemoveAll(parameter => ParameterType.HttpHeader.Equals(parameter.Type)
&& parameter.Name == SecurityTokenHeaderName);
restRequest.AddHeader(SecurityTokenHeaderName, sessionToken);
}
/// <summary>
/// Calculates AWS4 signature for the string, prepared for signing
/// </summary>
/// <param name="stringToSign">String to be signed</param>
/// <param name="signingDate">Signing Date</param>
/// <param name="secretKey">Secret Key</param>
/// <param name="region">Region</param>
/// <returns>AWS4 Signature</returns>
public virtual string CalculateSignature(string stringToSign,
DateTime signingDate,
string secretKey,
string region)
{
string date = signingDate.ToString(ISO8601BasicDateFormat, CultureInfo.InvariantCulture);
byte[] kSecret = Encoding.UTF8.GetBytes(Scheme + secretKey);
byte[] kDate = Utils.GetKeyedHash(kSecret, date);
byte[] kRegion = Utils.GetKeyedHash(kDate, region);
byte[] kService = Utils.GetKeyedHash(kRegion, ServiceName);
byte[] kSigning = Utils.GetKeyedHash(kService, TerminationString);
// Calculate the signature
return Utils.ToHex(Utils.GetKeyedHash(kSigning, stringToSign));
}
/// <summary>
/// Add a signature to a request in the form of an 'Authorization' header
/// </summary>
/// <param name="restRequest">Request to be signed</param>
/// <param name="accessKeyId">Access Key Id</param>
/// <param name="signedHeaders">Signed Headers</param>
/// <param name="signature">The signature to add</param>
/// <param name="region">AWS region for the request</param>
/// <param name="signingDate">Signature date</param>
public virtual void AddSignature(IRestRequest restRequest,
string accessKeyId,
string signedHeaders,
string signature,
string region,
DateTime signingDate)
{
string scope = BuildScope(signingDate, region);
StringBuilder authorizationHeaderValueBuilder = new StringBuilder();
authorizationHeaderValueBuilder.AppendFormat("{0}-{1}", Scheme, Algorithm);
authorizationHeaderValueBuilder.AppendFormat(" {0}={1}/{2},", CredentialSubHeaderName, accessKeyId, scope);
authorizationHeaderValueBuilder.AppendFormat(" {0}={1},", SignedHeadersSubHeaderName, signedHeaders);
authorizationHeaderValueBuilder.AppendFormat(" {0}={1}", SignatureSubHeaderName, signature);
restRequest.AddHeader(AuthorizationHeaderName, authorizationHeaderValueBuilder.ToString());
}
private static string BuildScope(DateTime signingDate, string region)
{
return string.Format("{0}/{1}/{2}/{3}",
signingDate.ToString(ISO8601BasicDateFormat, CultureInfo.InvariantCulture),
region,
ServiceName,
TerminationString);
}
}
}