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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b8b705b7ccc4d893d12bcc41c42921b0d6d5c264fc24c8aa04195736f05ecaac
4
- data.tar.gz: ab36dadea138a0593bb84d123fbe667c37bcdbb3e626e05628bac05493fbd1e6
3
+ metadata.gz: a8f4bb2026a22b4dc16eb1e53bc233135225971f13d2e16e6df7874749095c9c
4
+ data.tar.gz: 7a13586ac15fa24cb1c005ba27909c8687fc7416bd0bb81e8e8b9c4a89e0a161
5
5
  SHA512:
6
- metadata.gz: 421a1d3f97ee0037d2ddeeed88e454dc7fdd88314d1503abe488b224793e63b2e131d197c67cce9efdc09bcf6c63560a976b72b6db636cf91f89afcdfce1b79e
7
- data.tar.gz: cbb4297ad962402d891593d6326d460df3fe919bd1b3151ca9b80d3aadcc4f14384a4e4826d71f4d0faac9cacad4093b43a6b9be5a11fa206a20f3fb5a00a5f4
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
- 1.3.0 (2021-09-01)
4
+
5
+ 1.3.0.crt (2021-08-04)
5
6
  ------------------
6
7
 
7
- * 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.
8
9
 
9
10
  1.2.4 (2021-07-08)
10
11
  ------------------
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.3.0
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
- 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,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, [])).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
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 = headers['x-amz-date']
215
- datetime ||= Time.now.utc.strftime("%Y%m%dT%H%M%SZ")
216
- date = datetime[0,8]
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['x-amz-content-sha256']
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
- sigv4_headers['x-amz-date'] = datetime
224
- sigv4_headers['x-amz-security-token'] = creds.session_token if creds.session_token
225
- 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
226
177
 
227
178
  headers = headers.merge(sigv4_headers) # merge so we do not modify given headers hash
228
179
 
229
- # compute signature parts
230
- creq = canonical_request(http_method, url, headers, content_sha256)
231
- sts = string_to_sign(datetime, creq)
232
- 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)
233
199
 
234
- # apply signature
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
- string_to_sign: sts,
245
- canonical_request: creq,
246
- 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}
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['x-amz-date']
382
- datetime ||= (options[:time] || Time.now).utc.strftime("%Y%m%dT%H%M%SZ")
383
- date = datetime[0,8]
289
+ datetime = headers.delete('x-amz-date')
290
+ datetime ||= (options[:time] || Time.now)
384
291
 
385
- content_sha256 = headers['x-amz-content-sha256']
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
- params = {}
390
- params['X-Amz-Algorithm'] = 'AWS4-HMAC-SHA256'
391
- params['X-Amz-Credential'] = credential(creds, date)
392
- params['X-Amz-Date'] = datetime
393
- params['X-Amz-Expires'] = extract_expires_in(options)
394
- params['X-Amz-Security-Token'] = creds.session_token if creds.session_token
395
- 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)
396
316
 
397
- params = params.map do |key, value|
398
- "#{uri_escape(key)}=#{uri_escape(value)}"
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 url.query
402
- url.query += '&' + params
403
- else
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
- def string_to_sign(datetime, canonical_request)
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
- # Note:
441
- # While headers need to be encoded under eventstream format,
442
- # payload used is already eventstream encoded (event without signature),
443
- # thus no extra encoding is needed.
444
- def event_string_to_sign(datetime, headers, payload, prior_signature, encoder)
445
- encoded_headers = encoder.encode_headers(
446
- Aws::EventStream::Message.new(headers: headers, payload: payload)
447
- )
448
- [
449
- "AWS4-HMAC-SHA256-PAYLOAD",
450
- datetime,
451
- credential_scope(datetime[0,8]),
452
- prior_signature,
453
- sha256_hexdigest(encoded_headers),
454
- sha256_hexdigest(payload)
455
- ].join("\n")
456
- end
457
-
458
- def credential_scope(date)
459
- [
460
- date,
461
- @region,
462
- @service,
463
- 'aws4_request',
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
- # converting signature from binary string to hex-encoded
486
- # string is handled at #sign_event instead. (Will be used
487
- # as next prior signature for event signing)
488
- def event_signature(secret_access_key, date, string_to_sign)
489
- k_date = hmac("AWS4" + secret_access_key, date)
490
- k_region = hmac(k_date, @region)
491
- k_service = hmac(k_region, @service)
492
- k_credentials = hmac(k_service, 'aws4_request')
493
- hmac(k_credentials, string_to_sign)
494
- end
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
- def canonical_header_value(value)
559
- value.match(/^".*"$/) ? value : value.gsub(/\s+/, ' ').strip
560
- 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')
561
373
 
562
- def host(uri)
563
- # Handles known and unknown URI schemes; default_port nil when unknown.
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
- # @param [File, Tempfile, IO#read, String] value
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
- def hmac(key, value)
591
- 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]
592
381
  end
593
382
 
594
- def hexhmac(key, value)
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 = "missing required option :service"
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
- if options[:region]
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 = "missing required option :http_method"
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 = "missing required option :url"
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.inject({}) do |hash, (key, value)|
645
- hash[key.downcase] = value
646
- hash
647
- end
438
+ (headers || {}).to_hash.transform_keys(&:downcase)
648
439
  end
649
440
 
650
- def extract_expires_in(options)
651
- case options[:expires_in]
652
- when nil then 900.to_s
653
- 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
654
456
  else
655
- msg = "expected :expires_in to be a number of seconds"
656
- raise ArgumentError, msg
457
+ OpenSSL::Digest::SHA256.hexdigest(value)
657
458
  end
658
459
  end
659
460
 
660
- def uri_escape(string)
661
- 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
662
468
  end
663
469
 
664
- def uri_escape_path(string)
665
- 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('/')
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
- def fetch_credentials
670
- credentials = @credentials_provider.credentials
671
- if credentials_set?(credentials)
672
- credentials
673
- else
674
- raise Errors::MissingCredentialsError,
675
- 'unable to sign request without credentials set'
676
- 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")
677
506
  end
678
507
 
679
- # Returns true if credentials are set (not nil or empty)
680
- # Credentials may not implement the Credentials interface
681
- # and may just be credential like Client response objects
682
- # (eg those returned by sts#assume_role)
683
- def credentials_set?(credentials)
684
- !credentials.access_key_id.nil? &&
685
- !credentials.access_key_id.empty? &&
686
- !credentials.secret_access_key.nil? &&
687
- !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)
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.3.0
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-01 00:00:00.000000000 Z
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
94
  rubygems_version: 3.1.6
71
- signing_key:
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