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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 46dbb72f9e31ff1022703f91ae7e1d2cfe78c78629e682fe99468933704aff62
4
- data.tar.gz: 963db4a8f39031b64398dea0d2ebc7167af20e79446e23bfd270c6cb85d4738f
3
+ metadata.gz: 9f4ebc6670c011eafdb6f2ca33ee5894dd723f10f1ad23ea296837bb41355a77
4
+ data.tar.gz: 6e69783f73330b8559390d56718e0fe48cb117f4fc07ae8c808a2d6ebea6263f
5
5
  SHA512:
6
- metadata.gz: '0142942e58db9971d8ceaa2aeb7c97d1cd58bdba463366fd06831b1769a3c124f6e1f1b082c4d1b5917f794ab70556f6bd78dbb67824a35e74ccbfc5bdaafa25'
7
- data.tar.gz: aca23ad7a8a98f24abdbbaa2afe9cde1430a7a10f627b63d7da56939665056ccc7a04226dc14e3793e3848032291aba990fc10576369f3974911593566ea9262
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-09-01)
5
+ 1.3.0.crt (2021-08-04)
10
6
  ------------------
11
7
 
12
- * Feature - AWS SDK for Ruby no longer supports Ruby runtime versions 1.9, 2.0, 2.1, and 2.2.
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.0
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
- options[:credentials] :
55
- Credentials.new(options)
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
@@ -32,6 +32,8 @@ module Aws
32
32
  # @return [String] For debugging purposes.
33
33
  attr_accessor :content_sha256
34
34
 
35
+ attr_accessor :extra
36
+
35
37
  end
36
38
  end
37
39
  end
@@ -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, [])).map(&:downcase))
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
- [:uri_escape_path, :apply_checksum_header].each do |opt|
130
- instance_variable_set("@#{opt}", options.key?(opt) ? !!options[opt] : true)
131
- end
132
-
133
- if options[:signing_algorithm] == :sigv4a
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 = headers['x-amz-date']
221
- datetime ||= Time.now.utc.strftime("%Y%m%dT%H%M%SZ")
222
- date = datetime[0,8]
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['x-amz-content-sha256']
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
- sigv4_headers['x-amz-date'] = datetime
230
- sigv4_headers['x-amz-security-token'] = creds.session_token if creds.session_token
231
- sigv4_headers['x-amz-content-sha256'] ||= content_sha256 if @apply_checksum_header
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
- # compute signature parts
236
- creq = canonical_request(http_method, url, headers, content_sha256)
237
- sts = string_to_sign(datetime, creq)
238
- sig = signature(creds.secret_access_key, date, sts)
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
- # apply signature
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
- string_to_sign: sts,
251
- canonical_request: creq,
252
- content_sha256: content_sha256
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['x-amz-date']
388
- datetime ||= (options[:time] || Time.now).utc.strftime("%Y%m%dT%H%M%SZ")
389
- date = datetime[0,8]
289
+ datetime = headers.delete('x-amz-date')
290
+ datetime ||= (options[:time] || Time.now)
390
291
 
391
- content_sha256 = headers['x-amz-content-sha256']
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
- params = {}
396
- params['X-Amz-Algorithm'] = 'AWS4-HMAC-SHA256'
397
- params['X-Amz-Credential'] = credential(creds, date)
398
- params['X-Amz-Date'] = datetime
399
- params['X-Amz-Expires'] = extract_expires_in(options)
400
- params['X-Amz-Security-Token'] = creds.session_token if creds.session_token
401
- params['X-Amz-SignedHeaders'] = signed_headers(headers)
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
- params = params.map do |key, value|
404
- "#{uri_escape(key)}=#{uri_escape(value)}"
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 url.query
408
- url.query += '&' + params
409
- else
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
- # Compared to original #string_to_sign at signature v4 algorithm
442
- # there is no canonical_request concept for an eventstream event,
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
- # Note:
447
- # While headers need to be encoded under eventstream format,
448
- # payload used is already eventstream encoded (event without signature),
449
- # thus no extra encoding is needed.
450
- def event_string_to_sign(datetime, headers, payload, prior_signature, encoder)
451
- encoded_headers = encoder.encode_headers(
452
- Aws::EventStream::Message.new(headers: headers, payload: payload)
453
- )
454
- [
455
- "AWS4-HMAC-SHA256-PAYLOAD",
456
- datetime,
457
- credential_scope(datetime[0,8]),
458
- prior_signature,
459
- sha256_hexdigest(encoded_headers),
460
- sha256_hexdigest(payload)
461
- ].join("\n")
462
- end
463
-
464
- def credential_scope(date)
465
- [
466
- date,
467
- @region,
468
- @service,
469
- 'aws4_request',
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
- # converting signature from binary string to hex-encoded
492
- # string is handled at #sign_event instead. (Will be used
493
- # as next prior signature for event signing)
494
- def event_signature(secret_access_key, date, string_to_sign)
495
- k_date = hmac("AWS4" + secret_access_key, date)
496
- k_region = hmac(k_date, @region)
497
- k_service = hmac(k_region, @service)
498
- k_credentials = hmac(k_service, 'aws4_request')
499
- hmac(k_credentials, string_to_sign)
500
- end
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
- def canonical_header_value(value)
565
- value.match(/^".*"$/) ? value : value.gsub(/\s+/, ' ').strip
566
- end
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
- def host(uri)
569
- # Handles known and unknown URI schemes; default_port nil when unknown.
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
- # @param [File, Tempfile, IO#read, String] value
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
- def hmac(key, value)
597
- OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha256'), key, value)
379
+ # Returning signed headers and signature value in hex-encoded string
380
+ [headers, sig.unpack('H*').first]
598
381
  end
599
382
 
600
- def hexhmac(key, value)
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 = "missing required option :service"
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
- if options[:region]
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 = "missing required option :http_method"
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 = "missing required option :url"
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.inject({}) do |hash, (key, value)|
651
- hash[key.downcase] = value
652
- hash
653
- end
438
+ (headers || {}).to_hash.transform_keys(&:downcase)
654
439
  end
655
440
 
656
- def extract_expires_in(options)
657
- case options[:expires_in]
658
- when nil then 900.to_s
659
- when Integer then options[:expires_in].to_s
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
- msg = "expected :expires_in to be a number of seconds"
662
- raise ArgumentError, msg
457
+ OpenSSL::Digest::SHA256.hexdigest(value)
663
458
  end
664
459
  end
665
460
 
666
- def uri_escape(string)
667
- self.class.uri_escape(string)
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
- def uri_escape_path(string)
671
- self.class.uri_escape_path(string)
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
- def fetch_credentials
676
- credentials = @credentials_provider.credentials
677
- if credentials_set?(credentials)
678
- credentials
679
- else
680
- raise Errors::MissingCredentialsError,
681
- 'unable to sign request without credentials set'
682
- end
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
- # Returns true if credentials are set (not nil or empty)
686
- # Credentials may not implement the Credentials interface
687
- # and may just be credential like Client response objects
688
- # (eg those returned by sts#assume_role)
689
- def credentials_set?(credentials)
690
- !credentials.access_key_id.nil? &&
691
- !credentials.access_key_id.empty? &&
692
- !credentials.secret_access_key.nil? &&
693
- !credentials.secret_access_key.empty?
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.0
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
- description: Amazon Web Services Signature Version 4 signing library. Generates sigv4
34
- signature for HTTP requests.
35
- email:
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/aws/aws-sdk-ruby
75
+ homepage: https://github.com/awslabs/aws-crt-ruby
50
76
  licenses:
51
77
  - Apache-2.0
52
- metadata:
53
- source_code_uri: https://github.com/aws/aws-sdk-ruby/tree/version-3/gems/aws-sigv4
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.3'
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: '0'
92
+ version: 1.3.1
69
93
  requirements: []
70
- rubygems_version: 3.1.6
71
- signing_key:
94
+ rubygems_version: 3.2.7
95
+ signing_key:
72
96
  specification_version: 4
73
- summary: AWS Signature Version 4 library.
97
+ summary: AWS SDK for Ruby - Common Runtime (CRT) based Signer
74
98
  test_files: []
@@ -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