aws-sigv4 1.4.0 → 1.4.1.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 +2 -6
- 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 -389
- 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: 9f4ebc6670c011eafdb6f2ca33ee5894dd723f10f1ad23ea296837bb41355a77
|
4
|
+
data.tar.gz: 6e69783f73330b8559390d56718e0fe48cb117f4fc07ae8c808a2d6ebea6263f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2ba3f4da7f88b55f0529e9d23a4cc6a039abb6e1af7d939a9fe2679232c28ba80f7b2791758d39f67940cfa180c631f92ec424753a38f346b290132a95263ba6
|
7
|
+
data.tar.gz: 9fdee7aeff9a4764bb7a2395628ba5a9e52b8fe1e21adf165b48ed6241e3a2b71b7cff2d7597dba2696e49009963589244410e276898a3807945cb659865a0b6
|
data/CHANGELOG.md
CHANGED
@@ -1,15 +1,11 @@
|
|
1
1
|
Unreleased Changes
|
2
2
|
------------------
|
3
3
|
|
4
|
-
1.4.0 (2021-09-02)
|
5
|
-
------------------
|
6
|
-
|
7
|
-
* Feature - add `signing_algorithm` option with `sigv4` default.
|
8
4
|
|
9
|
-
1.3.0 (2021-
|
5
|
+
1.3.0.crt (2021-08-04)
|
10
6
|
------------------
|
11
7
|
|
12
|
-
* 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.
|
13
9
|
|
14
10
|
1.2.4 (2021-07-08)
|
15
11
|
------------------
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
1.4.
|
1
|
+
1.4.1.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,23 +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
|
-
|
132
|
-
|
133
|
-
|
134
|
-
raise ArgumentError, 'You are attempting to sign a' \
|
135
|
-
' request with sigv4a which requires aws-crt and version 1.4.0.crt or later of the aws-sigv4 gem.'\
|
136
|
-
' Please install the gem or add it to your gemfile.'
|
137
|
-
end
|
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)
|
138
79
|
end
|
139
80
|
|
140
81
|
# @return [String]
|
@@ -210,102 +151,65 @@ module Aws
|
|
210
151
|
# a `#headers` method. The headers must be applied to your request.
|
211
152
|
#
|
212
153
|
def sign_request(request)
|
213
|
-
|
214
154
|
creds = fetch_credentials
|
215
155
|
|
216
156
|
http_method = extract_http_method(request)
|
217
157
|
url = extract_url(request)
|
218
158
|
headers = downcase_headers(request[:headers])
|
219
159
|
|
220
|
-
datetime =
|
221
|
-
|
222
|
-
|
160
|
+
datetime =
|
161
|
+
if headers.include? 'x-amz-date'
|
162
|
+
Time.parse(headers.delete('x-amz-date'))
|
163
|
+
end
|
223
164
|
|
224
|
-
content_sha256 = headers
|
165
|
+
content_sha256 = headers.delete('x-amz-content-sha256')
|
225
166
|
content_sha256 ||= sha256_hexdigest(request[:body] || '')
|
226
167
|
|
227
168
|
sigv4_headers = {}
|
228
169
|
sigv4_headers['host'] = headers['host'] || host(url)
|
229
|
-
|
230
|
-
|
231
|
-
|
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
|
232
177
|
|
233
178
|
headers = headers.merge(sigv4_headers) # merge so we do not modify given headers hash
|
234
179
|
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
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)
|
239
199
|
|
240
|
-
|
241
|
-
sigv4_headers['authorization'] = [
|
242
|
-
"AWS4-HMAC-SHA256 Credential=#{credential(creds, date)}",
|
243
|
-
"SignedHeaders=#{signed_headers(headers)}",
|
244
|
-
"Signature=#{sig}",
|
245
|
-
].join(', ')
|
200
|
+
signing_result = Aws::Crt::Auth::Signer.sign_request(config, signable)
|
246
201
|
|
247
|
-
# Returning the signature components.
|
248
202
|
Signature.new(
|
249
|
-
headers: sigv4_headers
|
250
|
-
|
251
|
-
|
252
|
-
|
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}
|
253
210
|
)
|
254
211
|
end
|
255
212
|
|
256
|
-
# Signs a event and returns signature headers and prior signature
|
257
|
-
# used for next event signing.
|
258
|
-
#
|
259
|
-
# Headers of a sigv4 signed event message only contains 2 headers
|
260
|
-
# * ':chunk-signature'
|
261
|
-
# * computed signature of the event, binary string, 'bytes' type
|
262
|
-
# * ':date'
|
263
|
-
# * millisecond since epoch, 'timestamp' type
|
264
|
-
#
|
265
|
-
# Payload of the sigv4 signed event message contains eventstream encoded message
|
266
|
-
# which is serialized based on input and protocol
|
267
|
-
#
|
268
|
-
# To sign events
|
269
|
-
#
|
270
|
-
# headers_0, signature_0 = signer.sign_event(
|
271
|
-
# prior_signature, # hex-encoded string
|
272
|
-
# payload_0, # binary string (eventstream encoded event 0)
|
273
|
-
# encoder, # Aws::EventStreamEncoder
|
274
|
-
# )
|
275
|
-
#
|
276
|
-
# headers_1, signature_1 = signer.sign_event(
|
277
|
-
# signature_0,
|
278
|
-
# payload_1, # binary string (eventstream encoded event 1)
|
279
|
-
# encoder
|
280
|
-
# )
|
281
|
-
#
|
282
|
-
# The initial prior_signature should be using the signature computed at initial request
|
283
|
-
#
|
284
|
-
# Note:
|
285
|
-
#
|
286
|
-
# Since ':chunk-signature' header value has bytes type, the signature value provided
|
287
|
-
# needs to be a binary string instead of a hex-encoded string (like original signature
|
288
|
-
# V4 algorithm). Thus, when returning signature value used for next event siging, the
|
289
|
-
# signature value (a binary string) used at ':chunk-signature' needs to converted to
|
290
|
-
# hex-encoded string using #unpack
|
291
|
-
def sign_event(prior_signature, payload, encoder)
|
292
|
-
creds = fetch_credentials
|
293
|
-
time = Time.now
|
294
|
-
headers = {}
|
295
|
-
|
296
|
-
datetime = time.utc.strftime("%Y%m%dT%H%M%SZ")
|
297
|
-
date = datetime[0,8]
|
298
|
-
headers[':date'] = Aws::EventStream::HeaderValue.new(value: time.to_i * 1000, type: 'timestamp')
|
299
|
-
|
300
|
-
sts = event_string_to_sign(datetime, headers, payload, prior_signature, encoder)
|
301
|
-
sig = event_signature(creds.secret_access_key, date, sts)
|
302
|
-
|
303
|
-
headers[':chunk-signature'] = Aws::EventStream::HeaderValue.new(value: sig, type: 'bytes')
|
304
|
-
|
305
|
-
# Returning signed headers and signature value in hex-encoded string
|
306
|
-
[headers, sig.unpack('H*').first]
|
307
|
-
end
|
308
|
-
|
309
213
|
# Signs a URL with query authentication. Using query parameters
|
310
214
|
# to authenticate requests is useful when you want to express a
|
311
215
|
# request entirely in a URL. This method is also referred as
|
@@ -375,247 +279,120 @@ module Aws
|
|
375
279
|
# @return [HTTPS::URI, HTTP::URI]
|
376
280
|
#
|
377
281
|
def presign_url(options)
|
378
|
-
|
379
282
|
creds = fetch_credentials
|
380
283
|
|
381
284
|
http_method = extract_http_method(options)
|
382
285
|
url = extract_url(options)
|
383
|
-
|
384
286
|
headers = downcase_headers(options[:headers])
|
385
287
|
headers['host'] ||= host(url)
|
386
288
|
|
387
|
-
datetime = headers
|
388
|
-
datetime ||= (options[:time] || Time.now)
|
389
|
-
date = datetime[0,8]
|
289
|
+
datetime = headers.delete('x-amz-date')
|
290
|
+
datetime ||= (options[:time] || Time.now)
|
390
291
|
|
391
|
-
content_sha256 = headers
|
292
|
+
content_sha256 = headers.delete('x-amz-content-sha256')
|
392
293
|
content_sha256 ||= options[:body_digest]
|
393
294
|
content_sha256 ||= sha256_hexdigest(options[:body] || '')
|
394
295
|
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
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)
|
402
316
|
|
403
|
-
|
404
|
-
|
405
|
-
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])
|
406
319
|
|
407
|
-
if
|
408
|
-
|
409
|
-
|
410
|
-
url.query = params
|
320
|
+
if options[:extra] && options[:extra].is_a?(Hash)
|
321
|
+
options[:extra][:config] = config
|
322
|
+
options[:extra][:signable] = signable
|
411
323
|
end
|
412
|
-
|
413
|
-
creq = canonical_request(http_method, url, headers, content_sha256)
|
414
|
-
sts = string_to_sign(datetime, creq)
|
415
|
-
url.query += '&X-Amz-Signature=' + signature(creds.secret_access_key, date, sts)
|
416
324
|
url
|
417
325
|
end
|
418
326
|
|
419
|
-
private
|
420
|
-
|
421
|
-
def canonical_request(http_method, url, headers, content_sha256)
|
422
|
-
[
|
423
|
-
http_method,
|
424
|
-
path(url),
|
425
|
-
normalized_querystring(url.query || ''),
|
426
|
-
canonical_headers(headers) + "\n",
|
427
|
-
signed_headers(headers),
|
428
|
-
content_sha256,
|
429
|
-
].join("\n")
|
430
|
-
end
|
431
|
-
|
432
|
-
def string_to_sign(datetime, canonical_request)
|
433
|
-
[
|
434
|
-
'AWS4-HMAC-SHA256',
|
435
|
-
datetime,
|
436
|
-
credential_scope(datetime[0,8]),
|
437
|
-
sha256_hexdigest(canonical_request),
|
438
|
-
].join("\n")
|
439
|
-
end
|
440
327
|
|
441
|
-
#
|
442
|
-
#
|
443
|
-
# instead, an event contains headers and payload two parts, and
|
444
|
-
# 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.
|
445
330
|
#
|
446
|
-
#
|
447
|
-
#
|
448
|
-
#
|
449
|
-
#
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
].join('/')
|
471
|
-
end
|
472
|
-
|
473
|
-
def credential(credentials, date)
|
474
|
-
"#{credentials.access_key_id}/#{credential_scope(date)}"
|
475
|
-
end
|
476
|
-
|
477
|
-
def signature(secret_access_key, date, string_to_sign)
|
478
|
-
k_date = hmac("AWS4" + secret_access_key, date)
|
479
|
-
k_region = hmac(k_date, @region)
|
480
|
-
k_service = hmac(k_region, @service)
|
481
|
-
k_credentials = hmac(k_service, 'aws4_request')
|
482
|
-
hexhmac(k_credentials, string_to_sign)
|
483
|
-
end
|
484
|
-
|
485
|
-
# Comparing to original signature v4 algorithm,
|
486
|
-
# returned signature is a binary string instread of
|
487
|
-
# hex-encoded string. (Since ':chunk-signature' requires
|
488
|
-
# '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
|
489
355
|
#
|
490
356
|
# Note:
|
491
|
-
#
|
492
|
-
#
|
493
|
-
#
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
def path(url)
|
504
|
-
path = url.path
|
505
|
-
path = '/' if path == ''
|
506
|
-
if @uri_escape_path
|
507
|
-
uri_escape_path(path)
|
508
|
-
else
|
509
|
-
path
|
510
|
-
end
|
511
|
-
end
|
512
|
-
|
513
|
-
def normalized_querystring(querystring)
|
514
|
-
params = querystring.split('&')
|
515
|
-
params = params.map { |p| p.match(/=/) ? p : p + '=' }
|
516
|
-
# From: https://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html
|
517
|
-
# Sort the parameter names by character code point in ascending order.
|
518
|
-
# Parameters with duplicate names should be sorted by value.
|
519
|
-
#
|
520
|
-
# Default sort <=> in JRuby will swap members
|
521
|
-
# occasionally when <=> is 0 (considered still sorted), but this
|
522
|
-
# causes our normalized query string to not match the sent querystring.
|
523
|
-
# When names match, we then sort by their values. When values also
|
524
|
-
# match then we sort by their original order
|
525
|
-
params.each.with_index.sort do |a, b|
|
526
|
-
a, a_offset = a
|
527
|
-
b, b_offset = b
|
528
|
-
a_name, a_value = a.split('=')
|
529
|
-
b_name, b_value = b.split('=')
|
530
|
-
if a_name == b_name
|
531
|
-
if a_value == b_value
|
532
|
-
a_offset <=> b_offset
|
533
|
-
else
|
534
|
-
a_value <=> b_value
|
535
|
-
end
|
536
|
-
else
|
537
|
-
a_name <=> b_name
|
538
|
-
end
|
539
|
-
end.map(&:first).join('&')
|
540
|
-
end
|
541
|
-
|
542
|
-
def signed_headers(headers)
|
543
|
-
headers.inject([]) do |signed_headers, (header, _)|
|
544
|
-
if @unsigned_headers.include?(header)
|
545
|
-
signed_headers
|
546
|
-
else
|
547
|
-
signed_headers << header
|
548
|
-
end
|
549
|
-
end.sort.join(';')
|
550
|
-
end
|
551
|
-
|
552
|
-
def canonical_headers(headers)
|
553
|
-
headers = headers.inject([]) do |hdrs, (k,v)|
|
554
|
-
if @unsigned_headers.include?(k)
|
555
|
-
hdrs
|
556
|
-
else
|
557
|
-
hdrs << [k,v]
|
558
|
-
end
|
559
|
-
end
|
560
|
-
headers = headers.sort_by(&:first)
|
561
|
-
headers.map{|k,v| "#{k}:#{canonical_header_value(v.to_s)}" }.join("\n")
|
562
|
-
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 = {}
|
563
369
|
|
564
|
-
|
565
|
-
|
566
|
-
|
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')
|
567
373
|
|
568
|
-
|
569
|
-
|
570
|
-
if uri.default_port == uri.port
|
571
|
-
uri.host
|
572
|
-
else
|
573
|
-
"#{uri.host}:#{uri.port}"
|
574
|
-
end
|
575
|
-
end
|
374
|
+
sts = event_string_to_sign(datetime, headers, payload, prior_signature, encoder)
|
375
|
+
sig = event_signature(creds.secret_access_key, date, sts)
|
576
376
|
|
577
|
-
|
578
|
-
# @return [String<SHA256 Hexdigest>]
|
579
|
-
def sha256_hexdigest(value)
|
580
|
-
if (File === value || Tempfile === value) && !value.path.nil? && File.exist?(value.path)
|
581
|
-
OpenSSL::Digest::SHA256.file(value).hexdigest
|
582
|
-
elsif value.respond_to?(:read)
|
583
|
-
sha256 = OpenSSL::Digest::SHA256.new
|
584
|
-
loop do
|
585
|
-
chunk = value.read(1024 * 1024) # 1MB
|
586
|
-
break unless chunk
|
587
|
-
sha256.update(chunk)
|
588
|
-
end
|
589
|
-
value.rewind
|
590
|
-
sha256.hexdigest
|
591
|
-
else
|
592
|
-
OpenSSL::Digest::SHA256.hexdigest(value)
|
593
|
-
end
|
594
|
-
end
|
377
|
+
headers[':chunk-signature'] = Aws::EventStream::HeaderValue.new(value: sig, type: 'bytes')
|
595
378
|
|
596
|
-
|
597
|
-
|
379
|
+
# Returning signed headers and signature value in hex-encoded string
|
380
|
+
[headers, sig.unpack('H*').first]
|
598
381
|
end
|
599
382
|
|
600
|
-
|
601
|
-
OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha256'), key, value)
|
602
|
-
end
|
383
|
+
private
|
603
384
|
|
604
385
|
def extract_service(options)
|
605
386
|
if options[:service]
|
606
387
|
options[:service]
|
607
388
|
else
|
608
|
-
msg =
|
389
|
+
msg = 'missing required option :service'
|
609
390
|
raise ArgumentError, msg
|
610
391
|
end
|
611
392
|
end
|
612
393
|
|
613
394
|
def extract_region(options)
|
614
|
-
|
615
|
-
options[:region]
|
616
|
-
else
|
617
|
-
raise Errors::MissingRegionError
|
618
|
-
end
|
395
|
+
options[:region] || raise(Errors::MissingRegionError)
|
619
396
|
end
|
620
397
|
|
621
398
|
def extract_credentials_provider(options)
|
@@ -628,11 +405,22 @@ module Aws
|
|
628
405
|
end
|
629
406
|
end
|
630
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
|
+
|
631
419
|
def extract_http_method(request)
|
632
420
|
if request[:http_method]
|
633
421
|
request[:http_method].upcase
|
634
422
|
else
|
635
|
-
msg =
|
423
|
+
msg = 'missing required option :http_method'
|
636
424
|
raise ArgumentError, msg
|
637
425
|
end
|
638
426
|
end
|
@@ -641,56 +429,97 @@ module Aws
|
|
641
429
|
if request[:url]
|
642
430
|
URI.parse(request[:url].to_s)
|
643
431
|
else
|
644
|
-
msg =
|
432
|
+
msg = 'missing required option :url'
|
645
433
|
raise ArgumentError, msg
|
646
434
|
end
|
647
435
|
end
|
648
436
|
|
649
437
|
def downcase_headers(headers)
|
650
|
-
(headers || {}).to_hash.
|
651
|
-
hash[key.downcase] = value
|
652
|
-
hash
|
653
|
-
end
|
438
|
+
(headers || {}).to_hash.transform_keys(&:downcase)
|
654
439
|
end
|
655
440
|
|
656
|
-
|
657
|
-
|
658
|
-
|
659
|
-
|
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
|
660
456
|
else
|
661
|
-
|
662
|
-
raise ArgumentError, msg
|
457
|
+
OpenSSL::Digest::SHA256.hexdigest(value)
|
663
458
|
end
|
664
459
|
end
|
665
460
|
|
666
|
-
def
|
667
|
-
|
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
|
668
468
|
end
|
669
469
|
|
670
|
-
|
671
|
-
|
470
|
+
# Used only for event signing
|
471
|
+
def credential_scope(date)
|
472
|
+
[
|
473
|
+
date,
|
474
|
+
@region,
|
475
|
+
@service,
|
476
|
+
'aws4_request',
|
477
|
+
].join('/')
|
672
478
|
end
|
673
479
|
|
480
|
+
# Used only for event signing
|
481
|
+
def hmac(key, value)
|
482
|
+
OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha256'), key, value)
|
483
|
+
end
|
674
484
|
|
675
|
-
|
676
|
-
|
677
|
-
|
678
|
-
|
679
|
-
|
680
|
-
|
681
|
-
|
682
|
-
|
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")
|
683
506
|
end
|
684
507
|
|
685
|
-
#
|
686
|
-
#
|
687
|
-
#
|
688
|
-
#
|
689
|
-
|
690
|
-
|
691
|
-
|
692
|
-
|
693
|
-
|
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)
|
694
523
|
end
|
695
524
|
|
696
525
|
class << self
|
@@ -708,8 +537,8 @@ module Aws
|
|
708
537
|
CGI.escape(string.encode('UTF-8')).gsub('+', '%20').gsub('%7E', '~')
|
709
538
|
end
|
710
539
|
end
|
711
|
-
|
712
540
|
end
|
713
541
|
end
|
714
542
|
end
|
715
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.
|
4
|
+
version: 1.4.1.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
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
|
-
rubygems_version: 3.
|
71
|
-
signing_key:
|
94
|
+
rubygems_version: 3.2.7
|
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
|