aws-sigv4 1.3.0 → 1.4.0.crt

Sign up to get free protection for your applications and to get access to all the features.
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