aws-sigv4 1.3.0 → 1.4.0.crt
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +3 -2
- data/VERSION +1 -1
- data/lib/aws-sigv4/credentials.rb +6 -4
- data/lib/aws-sigv4/signature.rb +2 -0
- data/lib/aws-sigv4/signer.rb +218 -383
- data/lib/aws-sigv4.rb +7 -0
- metadata +41 -17
- data/lib/aws-sigv4/request.rb +0 -65
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a8f4bb2026a22b4dc16eb1e53bc233135225971f13d2e16e6df7874749095c9c
|
4
|
+
data.tar.gz: 7a13586ac15fa24cb1c005ba27909c8687fc7416bd0bb81e8e8b9c4a89e0a161
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f4720ab9cd966559519b0c803fc183f3ddbfa5c60bbdd0490fdef32b5d7ba0790438089d8b25130c4d40f32e81e42808e6a30cade92d3f0252d0131d53fcedc4
|
7
|
+
data.tar.gz: e257d60db8a752a75a539aad1571595a8367bd3e7b4a1f1e25a81c8cc00393e787e55d8f28554e294fc6babf9048ed10e2536d40d37ba241bb931be22dd42e1e
|
data/CHANGELOG.md
CHANGED
@@ -1,10 +1,11 @@
|
|
1
1
|
Unreleased Changes
|
2
2
|
------------------
|
3
3
|
|
4
|
-
|
4
|
+
|
5
|
+
1.3.0.crt (2021-08-04)
|
5
6
|
------------------
|
6
7
|
|
7
|
-
* Feature -
|
8
|
+
* Feature - Preview release of `aws-sigv4` version 1.3.0.crt gem - uses the Common Runtime (CRT) for signing and support for sigv4a.
|
8
9
|
|
9
10
|
1.2.4 (2021-07-08)
|
10
11
|
------------------
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
1.
|
1
|
+
1.4.0.crt
|
@@ -11,7 +11,10 @@ module Aws
|
|
11
11
|
# @option options [required, String] :secret_access_key
|
12
12
|
# @option options [String, nil] :session_token (nil)
|
13
13
|
def initialize(options = {})
|
14
|
-
if options[:access_key_id] && options[:secret_access_key]
|
14
|
+
if options[:access_key_id] && options[:secret_access_key] &&
|
15
|
+
!options[:access_key_id].empty? &&
|
16
|
+
!options[:secret_access_key].empty?
|
17
|
+
|
15
18
|
@access_key_id = options[:access_key_id]
|
16
19
|
@secret_access_key = options[:secret_access_key]
|
17
20
|
@session_token = options[:session_token]
|
@@ -51,8 +54,8 @@ module Aws
|
|
51
54
|
# @option options [String] :session_token (nil)
|
52
55
|
def initialize(options = {})
|
53
56
|
@credentials = options[:credentials] ?
|
54
|
-
|
55
|
-
|
57
|
+
options[:credentials] :
|
58
|
+
Credentials.new(options)
|
56
59
|
end
|
57
60
|
|
58
61
|
# @return [Credentials]
|
@@ -63,6 +66,5 @@ module Aws
|
|
63
66
|
!!credentials && credentials.set?
|
64
67
|
end
|
65
68
|
end
|
66
|
-
|
67
69
|
end
|
68
70
|
end
|
data/lib/aws-sigv4/signature.rb
CHANGED
data/lib/aws-sigv4/signer.rb
CHANGED
@@ -1,79 +1,16 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'openssl'
|
4
|
-
require 'tempfile'
|
5
4
|
require 'time'
|
5
|
+
require 'tempfile'
|
6
6
|
require 'uri'
|
7
7
|
require 'set'
|
8
|
-
require 'cgi'
|
9
8
|
require 'aws-eventstream'
|
10
9
|
|
11
10
|
module Aws
|
12
11
|
module Sigv4
|
13
|
-
|
14
|
-
# Utility class for creating AWS signature version 4 signature. This class
|
15
|
-
# provides two methods for generating signatures:
|
16
|
-
#
|
17
|
-
# * {#sign_request} - Computes a signature of the given request, returning
|
18
|
-
# the hash of headers that should be applied to the request.
|
19
|
-
#
|
20
|
-
# * {#presign_url} - Computes a presigned request with an expiration.
|
21
|
-
# By default, the body of this request is not signed and the request
|
22
|
-
# expires in 15 minutes.
|
23
|
-
#
|
24
|
-
# ## Configuration
|
25
|
-
#
|
26
|
-
# To use the signer, you need to specify the service, region, and credentials.
|
27
|
-
# The service name is normally the endpoint prefix to an AWS service. For
|
28
|
-
# example:
|
29
|
-
#
|
30
|
-
# ec2.us-west-1.amazonaws.com => ec2
|
31
|
-
#
|
32
|
-
# The region is normally the second portion of the endpoint, following
|
33
|
-
# the service name.
|
34
|
-
#
|
35
|
-
# ec2.us-west-1.amazonaws.com => us-west-1
|
36
|
-
#
|
37
|
-
# It is important to have the correct service and region name, or the
|
38
|
-
# signature will be invalid.
|
39
|
-
#
|
40
|
-
# ## Credentials
|
41
|
-
#
|
42
|
-
# The signer requires credentials. You can configure the signer
|
43
|
-
# with static credentials:
|
44
|
-
#
|
45
|
-
# signer = Aws::Sigv4::Signer.new(
|
46
|
-
# service: 's3',
|
47
|
-
# region: 'us-east-1',
|
48
|
-
# # static credentials
|
49
|
-
# access_key_id: 'akid',
|
50
|
-
# secret_access_key: 'secret'
|
51
|
-
# )
|
52
|
-
#
|
53
|
-
# You can also provide refreshing credentials via the `:credentials_provider`.
|
54
|
-
# If you are using the AWS SDK for Ruby, you can use any of the credential
|
55
|
-
# classes:
|
56
|
-
#
|
57
|
-
# signer = Aws::Sigv4::Signer.new(
|
58
|
-
# service: 's3',
|
59
|
-
# region: 'us-east-1',
|
60
|
-
# credentials_provider: Aws::InstanceProfileCredentials.new
|
61
|
-
# )
|
62
|
-
#
|
63
|
-
# Other AWS SDK for Ruby classes that can be provided via `:credentials_provider`:
|
64
|
-
#
|
65
|
-
# * `Aws::Credentials`
|
66
|
-
# * `Aws::SharedCredentials`
|
67
|
-
# * `Aws::InstanceProfileCredentials`
|
68
|
-
# * `Aws::AssumeRoleCredentials`
|
69
|
-
# * `Aws::ECSCredentials`
|
70
|
-
#
|
71
|
-
# A credential provider is any object that responds to `#credentials`
|
72
|
-
# returning another object that responds to `#access_key_id`, `#secret_access_key`,
|
73
|
-
# and `#session_token`.
|
74
|
-
#
|
12
|
+
# Utility class for creating AWS signature version 4 signature.
|
75
13
|
class Signer
|
76
|
-
|
77
14
|
# @overload initialize(service:, region:, access_key_id:, secret_access_key:, session_token:nil, **options)
|
78
15
|
# @param [String] :service The service signing name, e.g. 's3'.
|
79
16
|
# @param [String] :region The region name, e.g. 'us-east-1'.
|
@@ -118,17 +55,27 @@ module Aws
|
|
118
55
|
# headers. This is required for AWS Glacier, and optional for
|
119
56
|
# every other AWS service as of late 2016.
|
120
57
|
#
|
58
|
+
# @option options [Boolean] :omit_session_token (false) If `true`,
|
59
|
+
# then security token is added to the final signing result,
|
60
|
+
# but is treated as "unsigned" and does not contribute
|
61
|
+
# to the authorization signature.
|
62
|
+
#
|
63
|
+
# @option options [Boolean] :normalize_path (true) When `true`,
|
64
|
+
# the uri paths will be normalized when building the canonical request
|
121
65
|
def initialize(options = {})
|
122
66
|
@service = extract_service(options)
|
123
67
|
@region = extract_region(options)
|
124
68
|
@credentials_provider = extract_credentials_provider(options)
|
125
|
-
@unsigned_headers = Set.new((options.fetch(:unsigned_headers, []))
|
69
|
+
@unsigned_headers = Set.new((options.fetch(:unsigned_headers, []))
|
70
|
+
.map(&:downcase))
|
126
71
|
@unsigned_headers << 'authorization'
|
127
72
|
@unsigned_headers << 'x-amzn-trace-id'
|
128
73
|
@unsigned_headers << 'expect'
|
129
|
-
|
130
|
-
|
131
|
-
|
74
|
+
@uri_escape_path = options.fetch(:uri_escape_path, true)
|
75
|
+
@apply_checksum_header = options.fetch(:apply_checksum_header, true)
|
76
|
+
@signing_algorithm = options.fetch(:signing_algorithm, :sigv4)
|
77
|
+
@normalize_path = options.fetch(:normalize_path, true)
|
78
|
+
@omit_session_token = options.fetch(:omit_session_token, false)
|
132
79
|
end
|
133
80
|
|
134
81
|
# @return [String]
|
@@ -204,102 +151,65 @@ module Aws
|
|
204
151
|
# a `#headers` method. The headers must be applied to your request.
|
205
152
|
#
|
206
153
|
def sign_request(request)
|
207
|
-
|
208
154
|
creds = fetch_credentials
|
209
155
|
|
210
156
|
http_method = extract_http_method(request)
|
211
157
|
url = extract_url(request)
|
212
158
|
headers = downcase_headers(request[:headers])
|
213
159
|
|
214
|
-
datetime =
|
215
|
-
|
216
|
-
|
160
|
+
datetime =
|
161
|
+
if headers.include? 'x-amz-date'
|
162
|
+
Time.parse(headers.delete('x-amz-date'))
|
163
|
+
end
|
217
164
|
|
218
|
-
content_sha256 = headers
|
165
|
+
content_sha256 = headers.delete('x-amz-content-sha256')
|
219
166
|
content_sha256 ||= sha256_hexdigest(request[:body] || '')
|
220
167
|
|
221
168
|
sigv4_headers = {}
|
222
169
|
sigv4_headers['host'] = headers['host'] || host(url)
|
223
|
-
|
224
|
-
|
225
|
-
|
170
|
+
|
171
|
+
# Modify the user-agent to add usage of crt-signer
|
172
|
+
# This should be temporary during developer preview only
|
173
|
+
if headers.include? 'user-agent'
|
174
|
+
headers['user-agent'] = "#{headers['user-agent']} crt-signer/#{@signing_algorithm}/#{Aws::Sigv4::VERSION}"
|
175
|
+
sigv4_headers['user-agent'] = headers['user-agent']
|
176
|
+
end
|
226
177
|
|
227
178
|
headers = headers.merge(sigv4_headers) # merge so we do not modify given headers hash
|
228
179
|
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
180
|
+
config = Aws::Crt::Auth::SigningConfig.new(
|
181
|
+
algorithm: @signing_algorithm,
|
182
|
+
signature_type: :http_request_headers,
|
183
|
+
region: @region,
|
184
|
+
service: @service,
|
185
|
+
date: datetime,
|
186
|
+
signed_body_value: content_sha256,
|
187
|
+
signed_body_header_type: @apply_checksum_header ?
|
188
|
+
:sbht_content_sha256 : :sbht_none,
|
189
|
+
credentials: creds,
|
190
|
+
unsigned_headers: @unsigned_headers,
|
191
|
+
use_double_uri_encode: @uri_escape_path,
|
192
|
+
should_normalize_uri_path: @normalize_path,
|
193
|
+
omit_session_token: @omit_session_token
|
194
|
+
)
|
195
|
+
http_request = Aws::Crt::Http::Message.new(
|
196
|
+
http_method, url.to_s, headers
|
197
|
+
)
|
198
|
+
signable = Aws::Crt::Auth::Signable.new(http_request)
|
233
199
|
|
234
|
-
|
235
|
-
sigv4_headers['authorization'] = [
|
236
|
-
"AWS4-HMAC-SHA256 Credential=#{credential(creds, date)}",
|
237
|
-
"SignedHeaders=#{signed_headers(headers)}",
|
238
|
-
"Signature=#{sig}",
|
239
|
-
].join(', ')
|
200
|
+
signing_result = Aws::Crt::Auth::Signer.sign_request(config, signable)
|
240
201
|
|
241
|
-
# Returning the signature components.
|
242
202
|
Signature.new(
|
243
|
-
headers: sigv4_headers
|
244
|
-
|
245
|
-
|
246
|
-
|
203
|
+
headers: sigv4_headers.merge(
|
204
|
+
downcase_headers(signing_result[:headers])
|
205
|
+
),
|
206
|
+
string_to_sign: 'CRT_INTERNAL',
|
207
|
+
canonical_request: 'CRT_INTERNAL',
|
208
|
+
content_sha256: content_sha256,
|
209
|
+
extra: {config: config, signable: signable}
|
247
210
|
)
|
248
211
|
end
|
249
212
|
|
250
|
-
# Signs a event and returns signature headers and prior signature
|
251
|
-
# used for next event signing.
|
252
|
-
#
|
253
|
-
# Headers of a sigv4 signed event message only contains 2 headers
|
254
|
-
# * ':chunk-signature'
|
255
|
-
# * computed signature of the event, binary string, 'bytes' type
|
256
|
-
# * ':date'
|
257
|
-
# * millisecond since epoch, 'timestamp' type
|
258
|
-
#
|
259
|
-
# Payload of the sigv4 signed event message contains eventstream encoded message
|
260
|
-
# which is serialized based on input and protocol
|
261
|
-
#
|
262
|
-
# To sign events
|
263
|
-
#
|
264
|
-
# headers_0, signature_0 = signer.sign_event(
|
265
|
-
# prior_signature, # hex-encoded string
|
266
|
-
# payload_0, # binary string (eventstream encoded event 0)
|
267
|
-
# encoder, # Aws::EventStreamEncoder
|
268
|
-
# )
|
269
|
-
#
|
270
|
-
# headers_1, signature_1 = signer.sign_event(
|
271
|
-
# signature_0,
|
272
|
-
# payload_1, # binary string (eventstream encoded event 1)
|
273
|
-
# encoder
|
274
|
-
# )
|
275
|
-
#
|
276
|
-
# The initial prior_signature should be using the signature computed at initial request
|
277
|
-
#
|
278
|
-
# Note:
|
279
|
-
#
|
280
|
-
# Since ':chunk-signature' header value has bytes type, the signature value provided
|
281
|
-
# needs to be a binary string instead of a hex-encoded string (like original signature
|
282
|
-
# V4 algorithm). Thus, when returning signature value used for next event siging, the
|
283
|
-
# signature value (a binary string) used at ':chunk-signature' needs to converted to
|
284
|
-
# hex-encoded string using #unpack
|
285
|
-
def sign_event(prior_signature, payload, encoder)
|
286
|
-
creds = fetch_credentials
|
287
|
-
time = Time.now
|
288
|
-
headers = {}
|
289
|
-
|
290
|
-
datetime = time.utc.strftime("%Y%m%dT%H%M%SZ")
|
291
|
-
date = datetime[0,8]
|
292
|
-
headers[':date'] = Aws::EventStream::HeaderValue.new(value: time.to_i * 1000, type: 'timestamp')
|
293
|
-
|
294
|
-
sts = event_string_to_sign(datetime, headers, payload, prior_signature, encoder)
|
295
|
-
sig = event_signature(creds.secret_access_key, date, sts)
|
296
|
-
|
297
|
-
headers[':chunk-signature'] = Aws::EventStream::HeaderValue.new(value: sig, type: 'bytes')
|
298
|
-
|
299
|
-
# Returning signed headers and signature value in hex-encoded string
|
300
|
-
[headers, sig.unpack('H*').first]
|
301
|
-
end
|
302
|
-
|
303
213
|
# Signs a URL with query authentication. Using query parameters
|
304
214
|
# to authenticate requests is useful when you want to express a
|
305
215
|
# request entirely in a URL. This method is also referred as
|
@@ -369,247 +279,120 @@ module Aws
|
|
369
279
|
# @return [HTTPS::URI, HTTP::URI]
|
370
280
|
#
|
371
281
|
def presign_url(options)
|
372
|
-
|
373
282
|
creds = fetch_credentials
|
374
283
|
|
375
284
|
http_method = extract_http_method(options)
|
376
285
|
url = extract_url(options)
|
377
|
-
|
378
286
|
headers = downcase_headers(options[:headers])
|
379
287
|
headers['host'] ||= host(url)
|
380
288
|
|
381
|
-
datetime = headers
|
382
|
-
datetime ||= (options[:time] || Time.now)
|
383
|
-
date = datetime[0,8]
|
289
|
+
datetime = headers.delete('x-amz-date')
|
290
|
+
datetime ||= (options[:time] || Time.now)
|
384
291
|
|
385
|
-
content_sha256 = headers
|
292
|
+
content_sha256 = headers.delete('x-amz-content-sha256')
|
386
293
|
content_sha256 ||= options[:body_digest]
|
387
294
|
content_sha256 ||= sha256_hexdigest(options[:body] || '')
|
388
295
|
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
296
|
+
config = Aws::Crt::Auth::SigningConfig.new(
|
297
|
+
algorithm: @signing_algorithm,
|
298
|
+
signature_type: :http_request_query_params,
|
299
|
+
region: @region,
|
300
|
+
service: @service,
|
301
|
+
date: datetime,
|
302
|
+
signed_body_value: content_sha256,
|
303
|
+
signed_body_header_type: @apply_checksum_header ?
|
304
|
+
:sbht_content_sha256 : :sbht_none,
|
305
|
+
credentials: creds,
|
306
|
+
unsigned_headers: @unsigned_headers,
|
307
|
+
use_double_uri_encode: @uri_escape_path,
|
308
|
+
should_normalize_uri_path: @normalize_path,
|
309
|
+
omit_session_token: @omit_session_token,
|
310
|
+
expiration_in_seconds: options.fetch(:expires_in, 900)
|
311
|
+
)
|
312
|
+
http_request = Aws::Crt::Http::Message.new(
|
313
|
+
http_method, url.to_s, headers
|
314
|
+
)
|
315
|
+
signable = Aws::Crt::Auth::Signable.new(http_request)
|
396
316
|
|
397
|
-
|
398
|
-
|
399
|
-
end.join('&')
|
317
|
+
signing_result = Aws::Crt::Auth::Signer.sign_request(config, signable, http_method, url.to_s)
|
318
|
+
url = URI.parse(signing_result[:path])
|
400
319
|
|
401
|
-
if
|
402
|
-
|
403
|
-
|
404
|
-
url.query = params
|
320
|
+
if options[:extra] && options[:extra].is_a?(Hash)
|
321
|
+
options[:extra][:config] = config
|
322
|
+
options[:extra][:signable] = signable
|
405
323
|
end
|
406
|
-
|
407
|
-
creq = canonical_request(http_method, url, headers, content_sha256)
|
408
|
-
sts = string_to_sign(datetime, creq)
|
409
|
-
url.query += '&X-Amz-Signature=' + signature(creds.secret_access_key, date, sts)
|
410
324
|
url
|
411
325
|
end
|
412
326
|
|
413
|
-
private
|
414
|
-
|
415
|
-
def canonical_request(http_method, url, headers, content_sha256)
|
416
|
-
[
|
417
|
-
http_method,
|
418
|
-
path(url),
|
419
|
-
normalized_querystring(url.query || ''),
|
420
|
-
canonical_headers(headers) + "\n",
|
421
|
-
signed_headers(headers),
|
422
|
-
content_sha256,
|
423
|
-
].join("\n")
|
424
|
-
end
|
425
327
|
|
426
|
-
|
427
|
-
|
428
|
-
'AWS4-HMAC-SHA256',
|
429
|
-
datetime,
|
430
|
-
credential_scope(datetime[0,8]),
|
431
|
-
sha256_hexdigest(canonical_request),
|
432
|
-
].join("\n")
|
433
|
-
end
|
434
|
-
|
435
|
-
# Compared to original #string_to_sign at signature v4 algorithm
|
436
|
-
# there is no canonical_request concept for an eventstream event,
|
437
|
-
# instead, an event contains headers and payload two parts, and
|
438
|
-
# they will be used for computing digest in #event_string_to_sign
|
328
|
+
# Signs a event and returns signature headers and prior signature
|
329
|
+
# used for next event signing.
|
439
330
|
#
|
440
|
-
#
|
441
|
-
#
|
442
|
-
#
|
443
|
-
#
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
].join('/')
|
465
|
-
end
|
466
|
-
|
467
|
-
def credential(credentials, date)
|
468
|
-
"#{credentials.access_key_id}/#{credential_scope(date)}"
|
469
|
-
end
|
470
|
-
|
471
|
-
def signature(secret_access_key, date, string_to_sign)
|
472
|
-
k_date = hmac("AWS4" + secret_access_key, date)
|
473
|
-
k_region = hmac(k_date, @region)
|
474
|
-
k_service = hmac(k_region, @service)
|
475
|
-
k_credentials = hmac(k_service, 'aws4_request')
|
476
|
-
hexhmac(k_credentials, string_to_sign)
|
477
|
-
end
|
478
|
-
|
479
|
-
# Comparing to original signature v4 algorithm,
|
480
|
-
# returned signature is a binary string instread of
|
481
|
-
# hex-encoded string. (Since ':chunk-signature' requires
|
482
|
-
# 'bytes' type)
|
331
|
+
# Headers of a sigv4 signed event message only contains 2 headers
|
332
|
+
# * ':chunk-signature'
|
333
|
+
# * computed signature of the event, binary string, 'bytes' type
|
334
|
+
# * ':date'
|
335
|
+
# * millisecond since epoch, 'timestamp' type
|
336
|
+
#
|
337
|
+
# Payload of the sigv4 signed event message contains eventstream encoded message
|
338
|
+
# which is serialized based on input and protocol
|
339
|
+
#
|
340
|
+
# To sign events
|
341
|
+
#
|
342
|
+
# headers_0, signature_0 = signer.sign_event(
|
343
|
+
# prior_signature, # hex-encoded string
|
344
|
+
# payload_0, # binary string (eventstream encoded event 0)
|
345
|
+
# encoder, # Aws::EventStreamEncoder
|
346
|
+
# )
|
347
|
+
#
|
348
|
+
# headers_1, signature_1 = signer.sign_event(
|
349
|
+
# signature_0,
|
350
|
+
# payload_1, # binary string (eventstream encoded event 1)
|
351
|
+
# encoder
|
352
|
+
# )
|
353
|
+
#
|
354
|
+
# The initial prior_signature should be using the signature computed at initial request
|
483
355
|
#
|
484
356
|
# Note:
|
485
|
-
#
|
486
|
-
#
|
487
|
-
#
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
def path(url)
|
498
|
-
path = url.path
|
499
|
-
path = '/' if path == ''
|
500
|
-
if @uri_escape_path
|
501
|
-
uri_escape_path(path)
|
502
|
-
else
|
503
|
-
path
|
504
|
-
end
|
505
|
-
end
|
506
|
-
|
507
|
-
def normalized_querystring(querystring)
|
508
|
-
params = querystring.split('&')
|
509
|
-
params = params.map { |p| p.match(/=/) ? p : p + '=' }
|
510
|
-
# From: https://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html
|
511
|
-
# Sort the parameter names by character code point in ascending order.
|
512
|
-
# Parameters with duplicate names should be sorted by value.
|
513
|
-
#
|
514
|
-
# Default sort <=> in JRuby will swap members
|
515
|
-
# occasionally when <=> is 0 (considered still sorted), but this
|
516
|
-
# causes our normalized query string to not match the sent querystring.
|
517
|
-
# When names match, we then sort by their values. When values also
|
518
|
-
# match then we sort by their original order
|
519
|
-
params.each.with_index.sort do |a, b|
|
520
|
-
a, a_offset = a
|
521
|
-
b, b_offset = b
|
522
|
-
a_name, a_value = a.split('=')
|
523
|
-
b_name, b_value = b.split('=')
|
524
|
-
if a_name == b_name
|
525
|
-
if a_value == b_value
|
526
|
-
a_offset <=> b_offset
|
527
|
-
else
|
528
|
-
a_value <=> b_value
|
529
|
-
end
|
530
|
-
else
|
531
|
-
a_name <=> b_name
|
532
|
-
end
|
533
|
-
end.map(&:first).join('&')
|
534
|
-
end
|
535
|
-
|
536
|
-
def signed_headers(headers)
|
537
|
-
headers.inject([]) do |signed_headers, (header, _)|
|
538
|
-
if @unsigned_headers.include?(header)
|
539
|
-
signed_headers
|
540
|
-
else
|
541
|
-
signed_headers << header
|
542
|
-
end
|
543
|
-
end.sort.join(';')
|
544
|
-
end
|
545
|
-
|
546
|
-
def canonical_headers(headers)
|
547
|
-
headers = headers.inject([]) do |hdrs, (k,v)|
|
548
|
-
if @unsigned_headers.include?(k)
|
549
|
-
hdrs
|
550
|
-
else
|
551
|
-
hdrs << [k,v]
|
552
|
-
end
|
553
|
-
end
|
554
|
-
headers = headers.sort_by(&:first)
|
555
|
-
headers.map{|k,v| "#{k}:#{canonical_header_value(v.to_s)}" }.join("\n")
|
556
|
-
end
|
357
|
+
#
|
358
|
+
# Since ':chunk-signature' header value has bytes type, the signature value provided
|
359
|
+
# needs to be a binary string instead of a hex-encoded string (like original signature
|
360
|
+
# V4 algorithm). Thus, when returning signature value used for next event siging, the
|
361
|
+
# signature value (a binary string) used at ':chunk-signature' needs to converted to
|
362
|
+
# hex-encoded string using #unpack
|
363
|
+
def sign_event(prior_signature, payload, encoder)
|
364
|
+
# CRT does not currently provide event stream signing
|
365
|
+
# use the Ruby implementation
|
366
|
+
creds = @credentials_provider.credentials
|
367
|
+
time = Time.now
|
368
|
+
headers = {}
|
557
369
|
|
558
|
-
|
559
|
-
|
560
|
-
|
370
|
+
datetime = time.utc.strftime("%Y%m%dT%H%M%SZ")
|
371
|
+
date = datetime[0,8]
|
372
|
+
headers[':date'] = Aws::EventStream::HeaderValue.new(value: time.to_i * 1000, type: 'timestamp')
|
561
373
|
|
562
|
-
|
563
|
-
|
564
|
-
if uri.default_port == uri.port
|
565
|
-
uri.host
|
566
|
-
else
|
567
|
-
"#{uri.host}:#{uri.port}"
|
568
|
-
end
|
569
|
-
end
|
374
|
+
sts = event_string_to_sign(datetime, headers, payload, prior_signature, encoder)
|
375
|
+
sig = event_signature(creds.secret_access_key, date, sts)
|
570
376
|
|
571
|
-
|
572
|
-
# @return [String<SHA256 Hexdigest>]
|
573
|
-
def sha256_hexdigest(value)
|
574
|
-
if (File === value || Tempfile === value) && !value.path.nil? && File.exist?(value.path)
|
575
|
-
OpenSSL::Digest::SHA256.file(value).hexdigest
|
576
|
-
elsif value.respond_to?(:read)
|
577
|
-
sha256 = OpenSSL::Digest::SHA256.new
|
578
|
-
loop do
|
579
|
-
chunk = value.read(1024 * 1024) # 1MB
|
580
|
-
break unless chunk
|
581
|
-
sha256.update(chunk)
|
582
|
-
end
|
583
|
-
value.rewind
|
584
|
-
sha256.hexdigest
|
585
|
-
else
|
586
|
-
OpenSSL::Digest::SHA256.hexdigest(value)
|
587
|
-
end
|
588
|
-
end
|
377
|
+
headers[':chunk-signature'] = Aws::EventStream::HeaderValue.new(value: sig, type: 'bytes')
|
589
378
|
|
590
|
-
|
591
|
-
|
379
|
+
# Returning signed headers and signature value in hex-encoded string
|
380
|
+
[headers, sig.unpack('H*').first]
|
592
381
|
end
|
593
382
|
|
594
|
-
|
595
|
-
OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha256'), key, value)
|
596
|
-
end
|
383
|
+
private
|
597
384
|
|
598
385
|
def extract_service(options)
|
599
386
|
if options[:service]
|
600
387
|
options[:service]
|
601
388
|
else
|
602
|
-
msg =
|
389
|
+
msg = 'missing required option :service'
|
603
390
|
raise ArgumentError, msg
|
604
391
|
end
|
605
392
|
end
|
606
393
|
|
607
394
|
def extract_region(options)
|
608
|
-
|
609
|
-
options[:region]
|
610
|
-
else
|
611
|
-
raise Errors::MissingRegionError
|
612
|
-
end
|
395
|
+
options[:region] || raise(Errors::MissingRegionError)
|
613
396
|
end
|
614
397
|
|
615
398
|
def extract_credentials_provider(options)
|
@@ -622,11 +405,22 @@ module Aws
|
|
622
405
|
end
|
623
406
|
end
|
624
407
|
|
408
|
+
# the credentials used by CRT must be a
|
409
|
+
# CRT StaticCredentialsProvider object
|
410
|
+
def fetch_credentials
|
411
|
+
credentials = @credentials_provider.credentials
|
412
|
+
Aws::Crt::Auth::StaticCredentialsProvider.new(
|
413
|
+
credentials.access_key_id,
|
414
|
+
credentials.secret_access_key,
|
415
|
+
credentials.session_token
|
416
|
+
)
|
417
|
+
end
|
418
|
+
|
625
419
|
def extract_http_method(request)
|
626
420
|
if request[:http_method]
|
627
421
|
request[:http_method].upcase
|
628
422
|
else
|
629
|
-
msg =
|
423
|
+
msg = 'missing required option :http_method'
|
630
424
|
raise ArgumentError, msg
|
631
425
|
end
|
632
426
|
end
|
@@ -635,56 +429,97 @@ module Aws
|
|
635
429
|
if request[:url]
|
636
430
|
URI.parse(request[:url].to_s)
|
637
431
|
else
|
638
|
-
msg =
|
432
|
+
msg = 'missing required option :url'
|
639
433
|
raise ArgumentError, msg
|
640
434
|
end
|
641
435
|
end
|
642
436
|
|
643
437
|
def downcase_headers(headers)
|
644
|
-
(headers || {}).to_hash.
|
645
|
-
hash[key.downcase] = value
|
646
|
-
hash
|
647
|
-
end
|
438
|
+
(headers || {}).to_hash.transform_keys(&:downcase)
|
648
439
|
end
|
649
440
|
|
650
|
-
|
651
|
-
|
652
|
-
|
653
|
-
|
441
|
+
# @param [File, Tempfile, IO#read, String] value
|
442
|
+
# @return [String<SHA256 Hexdigest>]
|
443
|
+
def sha256_hexdigest(value)
|
444
|
+
if (value.is_a?(File) || value.is_a?(Tempfile)) && !value.path.nil? && File.exist?(value.path)
|
445
|
+
OpenSSL::Digest::SHA256.file(value).hexdigest
|
446
|
+
elsif value.respond_to?(:read)
|
447
|
+
sha256 = OpenSSL::Digest.new('SHA256')
|
448
|
+
loop do
|
449
|
+
chunk = value.read(1024 * 1024) # 1MB
|
450
|
+
break unless chunk
|
451
|
+
|
452
|
+
sha256.update(chunk)
|
453
|
+
end
|
454
|
+
value.rewind
|
455
|
+
sha256.hexdigest
|
654
456
|
else
|
655
|
-
|
656
|
-
raise ArgumentError, msg
|
457
|
+
OpenSSL::Digest::SHA256.hexdigest(value)
|
657
458
|
end
|
658
459
|
end
|
659
460
|
|
660
|
-
def
|
661
|
-
|
461
|
+
def host(uri)
|
462
|
+
# Handles known and unknown URI schemes; default_port nil when unknown.
|
463
|
+
if uri.default_port == uri.port
|
464
|
+
uri.host
|
465
|
+
else
|
466
|
+
"#{uri.host}:#{uri.port}"
|
467
|
+
end
|
662
468
|
end
|
663
469
|
|
664
|
-
|
665
|
-
|
470
|
+
# Used only for event signing
|
471
|
+
def credential_scope(date)
|
472
|
+
[
|
473
|
+
date,
|
474
|
+
@region,
|
475
|
+
@service,
|
476
|
+
'aws4_request',
|
477
|
+
].join('/')
|
666
478
|
end
|
667
479
|
|
480
|
+
# Used only for event signing
|
481
|
+
def hmac(key, value)
|
482
|
+
OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha256'), key, value)
|
483
|
+
end
|
668
484
|
|
669
|
-
|
670
|
-
|
671
|
-
|
672
|
-
|
673
|
-
|
674
|
-
|
675
|
-
|
676
|
-
|
485
|
+
# Compared to original #string_to_sign at signature v4 algorithm
|
486
|
+
# there is no canonical_request concept for an eventstream event,
|
487
|
+
# instead, an event contains headers and payload two parts, and
|
488
|
+
# they will be used for computing digest in #event_string_to_sign
|
489
|
+
#
|
490
|
+
# Note:
|
491
|
+
# While headers need to be encoded under eventstream format,
|
492
|
+
# payload used is already eventstream encoded (event without signature),
|
493
|
+
# thus no extra encoding is needed.
|
494
|
+
def event_string_to_sign(datetime, headers, payload, prior_signature, encoder)
|
495
|
+
encoded_headers = encoder.encode_headers(
|
496
|
+
Aws::EventStream::Message.new(headers: headers, payload: payload)
|
497
|
+
)
|
498
|
+
[
|
499
|
+
"AWS4-HMAC-SHA256-PAYLOAD",
|
500
|
+
datetime,
|
501
|
+
credential_scope(datetime[0,8]),
|
502
|
+
prior_signature,
|
503
|
+
sha256_hexdigest(encoded_headers),
|
504
|
+
sha256_hexdigest(payload)
|
505
|
+
].join("\n")
|
677
506
|
end
|
678
507
|
|
679
|
-
#
|
680
|
-
#
|
681
|
-
#
|
682
|
-
#
|
683
|
-
|
684
|
-
|
685
|
-
|
686
|
-
|
687
|
-
|
508
|
+
# Comparing to original signature v4 algorithm,
|
509
|
+
# returned signature is a binary string instread of
|
510
|
+
# hex-encoded string. (Since ':chunk-signature' requires
|
511
|
+
# 'bytes' type)
|
512
|
+
#
|
513
|
+
# Note:
|
514
|
+
# converting signature from binary string to hex-encoded
|
515
|
+
# string is handled at #sign_event instead. (Will be used
|
516
|
+
# as next prior signature for event signing)
|
517
|
+
def event_signature(secret_access_key, date, string_to_sign)
|
518
|
+
k_date = hmac("AWS4" + secret_access_key, date)
|
519
|
+
k_region = hmac(k_date, @region)
|
520
|
+
k_service = hmac(k_region, @service)
|
521
|
+
k_credentials = hmac(k_service, 'aws4_request')
|
522
|
+
hmac(k_credentials, string_to_sign)
|
688
523
|
end
|
689
524
|
|
690
525
|
class << self
|
@@ -702,8 +537,8 @@ module Aws
|
|
702
537
|
CGI.escape(string.encode('UTF-8')).gsub('+', '%20').gsub('%7E', '~')
|
703
538
|
end
|
704
539
|
end
|
705
|
-
|
706
540
|
end
|
707
541
|
end
|
708
542
|
end
|
709
543
|
end
|
544
|
+
|
data/lib/aws-sigv4.rb
CHANGED
@@ -1,6 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'aws-crt'
|
3
4
|
require_relative 'aws-sigv4/credentials'
|
4
5
|
require_relative 'aws-sigv4/errors'
|
5
6
|
require_relative 'aws-sigv4/signature'
|
6
7
|
require_relative 'aws-sigv4/signer'
|
8
|
+
|
9
|
+
module Aws
|
10
|
+
module Sigv4
|
11
|
+
VERSION = File.read(File.expand_path('../VERSION', __dir__)).strip
|
12
|
+
end
|
13
|
+
end
|
metadata
CHANGED
@@ -1,15 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: aws-sigv4
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.4.0.crt
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Amazon Web Services
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-09-
|
11
|
+
date: 2021-09-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: aws-crt
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
13
27
|
- !ruby/object:Gem::Dependency
|
14
28
|
name: aws-eventstream
|
15
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -30,9 +44,22 @@ dependencies:
|
|
30
44
|
- - ">="
|
31
45
|
- !ruby/object:Gem::Version
|
32
46
|
version: 1.0.2
|
33
|
-
|
34
|
-
|
35
|
-
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: rspec
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - ">="
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '0'
|
61
|
+
description: Amazon Web Services signing library. Generates signatures for HTTP requests
|
62
|
+
email:
|
36
63
|
executables: []
|
37
64
|
extensions: []
|
38
65
|
extra_rdoc_files: []
|
@@ -43,16 +70,13 @@ files:
|
|
43
70
|
- lib/aws-sigv4.rb
|
44
71
|
- lib/aws-sigv4/credentials.rb
|
45
72
|
- lib/aws-sigv4/errors.rb
|
46
|
-
- lib/aws-sigv4/request.rb
|
47
73
|
- lib/aws-sigv4/signature.rb
|
48
74
|
- lib/aws-sigv4/signer.rb
|
49
|
-
homepage: https://github.com/
|
75
|
+
homepage: https://github.com/awslabs/aws-crt-ruby
|
50
76
|
licenses:
|
51
77
|
- Apache-2.0
|
52
|
-
metadata:
|
53
|
-
|
54
|
-
changelog_uri: https://github.com/aws/aws-sdk-ruby/tree/version-3/gems/aws-sigv4/CHANGELOG.md
|
55
|
-
post_install_message:
|
78
|
+
metadata: {}
|
79
|
+
post_install_message:
|
56
80
|
rdoc_options: []
|
57
81
|
require_paths:
|
58
82
|
- lib
|
@@ -60,15 +84,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
60
84
|
requirements:
|
61
85
|
- - ">="
|
62
86
|
- !ruby/object:Gem::Version
|
63
|
-
version: '2.
|
87
|
+
version: '2.5'
|
64
88
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
65
89
|
requirements:
|
66
|
-
- - "
|
90
|
+
- - ">"
|
67
91
|
- !ruby/object:Gem::Version
|
68
|
-
version:
|
92
|
+
version: 1.3.1
|
69
93
|
requirements: []
|
70
94
|
rubygems_version: 3.1.6
|
71
|
-
signing_key:
|
95
|
+
signing_key:
|
72
96
|
specification_version: 4
|
73
|
-
summary: AWS
|
97
|
+
summary: AWS SDK for Ruby - Common Runtime (CRT) based Signer
|
74
98
|
test_files: []
|
data/lib/aws-sigv4/request.rb
DELETED
@@ -1,65 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'uri'
|
4
|
-
|
5
|
-
module Aws
|
6
|
-
module Sigv4
|
7
|
-
class Request
|
8
|
-
|
9
|
-
# @option options [required, String] :http_method
|
10
|
-
# @option options [required, HTTP::URI, HTTPS::URI, String] :endpoint
|
11
|
-
# @option options [Hash<String,String>] :headers ({})
|
12
|
-
# @option options [String, IO] :body ('')
|
13
|
-
def initialize(options = {})
|
14
|
-
@http_method = nil
|
15
|
-
@endpoint = nil
|
16
|
-
@headers = {}
|
17
|
-
@body = ''
|
18
|
-
options.each_pair do |attr_name, attr_value|
|
19
|
-
send("#{attr_name}=", attr_value)
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
# @param [String] http_method One of 'GET', 'PUT', 'POST', 'DELETE', 'HEAD', or 'PATCH'
|
24
|
-
def http_method=(http_method)
|
25
|
-
@http_method = http_method
|
26
|
-
end
|
27
|
-
|
28
|
-
# @return [String] One of 'GET', 'PUT', 'POST', 'DELETE', 'HEAD', or 'PATCH'
|
29
|
-
def http_method
|
30
|
-
@http_method
|
31
|
-
end
|
32
|
-
|
33
|
-
# @param [String, HTTP::URI, HTTPS::URI] endpoint
|
34
|
-
def endpoint=(endpoint)
|
35
|
-
@endpoint = URI.parse(endpoint.to_s)
|
36
|
-
end
|
37
|
-
|
38
|
-
# @return [HTTP::URI, HTTPS::URI]
|
39
|
-
def endpoint
|
40
|
-
@endpoint
|
41
|
-
end
|
42
|
-
|
43
|
-
# @param [Hash] headers
|
44
|
-
def headers=(headers)
|
45
|
-
@headers = headers
|
46
|
-
end
|
47
|
-
|
48
|
-
# @return [Hash<String,String>]
|
49
|
-
def headers
|
50
|
-
@headers
|
51
|
-
end
|
52
|
-
|
53
|
-
# @param [String, IO] body
|
54
|
-
def body=(body)
|
55
|
-
@body = body
|
56
|
-
end
|
57
|
-
|
58
|
-
# @return [String, IO]
|
59
|
-
def body
|
60
|
-
@body
|
61
|
-
end
|
62
|
-
|
63
|
-
end
|
64
|
-
end
|
65
|
-
end
|