aws-sigv4 1.4.0.crt → 1.4.0

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: a8f4bb2026a22b4dc16eb1e53bc233135225971f13d2e16e6df7874749095c9c
4
- data.tar.gz: 7a13586ac15fa24cb1c005ba27909c8687fc7416bd0bb81e8e8b9c4a89e0a161
3
+ metadata.gz: 46dbb72f9e31ff1022703f91ae7e1d2cfe78c78629e682fe99468933704aff62
4
+ data.tar.gz: 963db4a8f39031b64398dea0d2ebc7167af20e79446e23bfd270c6cb85d4738f
5
5
  SHA512:
6
- metadata.gz: f4720ab9cd966559519b0c803fc183f3ddbfa5c60bbdd0490fdef32b5d7ba0790438089d8b25130c4d40f32e81e42808e6a30cade92d3f0252d0131d53fcedc4
7
- data.tar.gz: e257d60db8a752a75a539aad1571595a8367bd3e7b4a1f1e25a81c8cc00393e787e55d8f28554e294fc6babf9048ed10e2536d40d37ba241bb931be22dd42e1e
6
+ metadata.gz: '0142942e58db9971d8ceaa2aeb7c97d1cd58bdba463366fd06831b1769a3c124f6e1f1b082c4d1b5917f794ab70556f6bd78dbb67824a35e74ccbfc5bdaafa25'
7
+ data.tar.gz: aca23ad7a8a98f24abdbbaa2afe9cde1430a7a10f627b63d7da56939665056ccc7a04226dc14e3793e3848032291aba990fc10576369f3974911593566ea9262
data/CHANGELOG.md CHANGED
@@ -1,11 +1,15 @@
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.
4
8
 
5
- 1.3.0.crt (2021-08-04)
9
+ 1.3.0 (2021-09-01)
6
10
  ------------------
7
11
 
8
- * Feature - Preview release of `aws-sigv4` version 1.3.0.crt gem - uses the Common Runtime (CRT) for signing and support for sigv4a.
12
+ * Feature - AWS SDK for Ruby no longer supports Ruby runtime versions 1.9, 2.0, 2.1, and 2.2.
9
13
 
10
14
  1.2.4 (2021-07-08)
11
15
  ------------------
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.4.0.crt
1
+ 1.4.0
@@ -11,10 +11,7 @@ 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] &&
15
- !options[:access_key_id].empty? &&
16
- !options[:secret_access_key].empty?
17
-
14
+ if options[:access_key_id] && options[:secret_access_key]
18
15
  @access_key_id = options[:access_key_id]
19
16
  @secret_access_key = options[:secret_access_key]
20
17
  @session_token = options[:session_token]
@@ -54,8 +51,8 @@ module Aws
54
51
  # @option options [String] :session_token (nil)
55
52
  def initialize(options = {})
56
53
  @credentials = options[:credentials] ?
57
- options[:credentials] :
58
- Credentials.new(options)
54
+ options[:credentials] :
55
+ Credentials.new(options)
59
56
  end
60
57
 
61
58
  # @return [Credentials]
@@ -66,5 +63,6 @@ module Aws
66
63
  !!credentials && credentials.set?
67
64
  end
68
65
  end
66
+
69
67
  end
70
68
  end
@@ -0,0 +1,65 @@
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
@@ -32,8 +32,6 @@ module Aws
32
32
  # @return [String] For debugging purposes.
33
33
  attr_accessor :content_sha256
34
34
 
35
- attr_accessor :extra
36
-
37
35
  end
38
36
  end
39
37
  end
@@ -1,16 +1,79 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'openssl'
4
- require 'time'
5
4
  require 'tempfile'
5
+ require 'time'
6
6
  require 'uri'
7
7
  require 'set'
8
+ require 'cgi'
8
9
  require 'aws-eventstream'
9
10
 
10
11
  module Aws
11
12
  module Sigv4
12
- # Utility class for creating AWS signature version 4 signature.
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
+ #
13
75
  class Signer
76
+
14
77
  # @overload initialize(service:, region:, access_key_id:, secret_access_key:, session_token:nil, **options)
15
78
  # @param [String] :service The service signing name, e.g. 's3'.
16
79
  # @param [String] :region The region name, e.g. 'us-east-1'.
@@ -55,27 +118,23 @@ module Aws
55
118
  # headers. This is required for AWS Glacier, and optional for
56
119
  # every other AWS service as of late 2016.
57
120
  #
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
65
121
  def initialize(options = {})
66
122
  @service = extract_service(options)
67
123
  @region = extract_region(options)
68
124
  @credentials_provider = extract_credentials_provider(options)
69
- @unsigned_headers = Set.new((options.fetch(:unsigned_headers, []))
70
- .map(&:downcase))
125
+ @unsigned_headers = Set.new((options.fetch(:unsigned_headers, [])).map(&:downcase))
71
126
  @unsigned_headers << 'authorization'
72
127
  @unsigned_headers << 'x-amzn-trace-id'
73
128
  @unsigned_headers << 'expect'
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)
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
79
138
  end
80
139
 
81
140
  # @return [String]
@@ -151,65 +210,102 @@ module Aws
151
210
  # a `#headers` method. The headers must be applied to your request.
152
211
  #
153
212
  def sign_request(request)
213
+
154
214
  creds = fetch_credentials
155
215
 
156
216
  http_method = extract_http_method(request)
157
217
  url = extract_url(request)
158
218
  headers = downcase_headers(request[:headers])
159
219
 
160
- datetime =
161
- if headers.include? 'x-amz-date'
162
- Time.parse(headers.delete('x-amz-date'))
163
- end
220
+ datetime = headers['x-amz-date']
221
+ datetime ||= Time.now.utc.strftime("%Y%m%dT%H%M%SZ")
222
+ date = datetime[0,8]
164
223
 
165
- content_sha256 = headers.delete('x-amz-content-sha256')
224
+ content_sha256 = headers['x-amz-content-sha256']
166
225
  content_sha256 ||= sha256_hexdigest(request[:body] || '')
167
226
 
168
227
  sigv4_headers = {}
169
228
  sigv4_headers['host'] = headers['host'] || host(url)
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
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
177
232
 
178
233
  headers = headers.merge(sigv4_headers) # merge so we do not modify given headers hash
179
234
 
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)
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)
199
239
 
200
- signing_result = Aws::Crt::Auth::Signer.sign_request(config, signable)
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(', ')
201
246
 
247
+ # Returning the signature components.
202
248
  Signature.new(
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}
249
+ headers: sigv4_headers,
250
+ string_to_sign: sts,
251
+ canonical_request: creq,
252
+ content_sha256: content_sha256
210
253
  )
211
254
  end
212
255
 
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
+
213
309
  # Signs a URL with query authentication. Using query parameters
214
310
  # to authenticate requests is useful when you want to express a
215
311
  # request entirely in a URL. This method is also referred as
@@ -279,120 +375,247 @@ module Aws
279
375
  # @return [HTTPS::URI, HTTP::URI]
280
376
  #
281
377
  def presign_url(options)
378
+
282
379
  creds = fetch_credentials
283
380
 
284
381
  http_method = extract_http_method(options)
285
382
  url = extract_url(options)
383
+
286
384
  headers = downcase_headers(options[:headers])
287
385
  headers['host'] ||= host(url)
288
386
 
289
- datetime = headers.delete('x-amz-date')
290
- datetime ||= (options[:time] || Time.now)
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]
291
390
 
292
- content_sha256 = headers.delete('x-amz-content-sha256')
391
+ content_sha256 = headers['x-amz-content-sha256']
293
392
  content_sha256 ||= options[:body_digest]
294
393
  content_sha256 ||= sha256_hexdigest(options[:body] || '')
295
394
 
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)
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)
316
402
 
317
- signing_result = Aws::Crt::Auth::Signer.sign_request(config, signable, http_method, url.to_s)
318
- url = URI.parse(signing_result[:path])
403
+ params = params.map do |key, value|
404
+ "#{uri_escape(key)}=#{uri_escape(value)}"
405
+ end.join('&')
319
406
 
320
- if options[:extra] && options[:extra].is_a?(Hash)
321
- options[:extra][:config] = config
322
- options[:extra][:signable] = signable
407
+ if url.query
408
+ url.query += '&' + params
409
+ else
410
+ url.query = params
323
411
  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)
324
416
  url
325
417
  end
326
418
 
419
+ private
327
420
 
328
- # Signs a event and returns signature headers and prior signature
329
- # used for next event signing.
330
- #
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
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
+
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
355
445
  #
356
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)
357
489
  #
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 = {}
490
+ # 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
369
501
 
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')
373
502
 
374
- sts = event_string_to_sign(datetime, headers, payload, prior_signature, encoder)
375
- sig = event_signature(creds.secret_access_key, date, sts)
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
376
512
 
377
- headers[':chunk-signature'] = Aws::EventStream::HeaderValue.new(value: sig, type: 'bytes')
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
378
541
 
379
- # Returning signed headers and signature value in hex-encoded string
380
- [headers, sig.unpack('H*').first]
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(';')
381
550
  end
382
551
 
383
- private
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
563
+
564
+ def canonical_header_value(value)
565
+ value.match(/^".*"$/) ? value : value.gsub(/\s+/, ' ').strip
566
+ end
567
+
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
576
+
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
595
+
596
+ def hmac(key, value)
597
+ OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha256'), key, value)
598
+ end
599
+
600
+ def hexhmac(key, value)
601
+ OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha256'), key, value)
602
+ end
384
603
 
385
604
  def extract_service(options)
386
605
  if options[:service]
387
606
  options[:service]
388
607
  else
389
- msg = 'missing required option :service'
608
+ msg = "missing required option :service"
390
609
  raise ArgumentError, msg
391
610
  end
392
611
  end
393
612
 
394
613
  def extract_region(options)
395
- options[:region] || raise(Errors::MissingRegionError)
614
+ if options[:region]
615
+ options[:region]
616
+ else
617
+ raise Errors::MissingRegionError
618
+ end
396
619
  end
397
620
 
398
621
  def extract_credentials_provider(options)
@@ -405,22 +628,11 @@ module Aws
405
628
  end
406
629
  end
407
630
 
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
-
419
631
  def extract_http_method(request)
420
632
  if request[:http_method]
421
633
  request[:http_method].upcase
422
634
  else
423
- msg = 'missing required option :http_method'
635
+ msg = "missing required option :http_method"
424
636
  raise ArgumentError, msg
425
637
  end
426
638
  end
@@ -429,97 +641,56 @@ module Aws
429
641
  if request[:url]
430
642
  URI.parse(request[:url].to_s)
431
643
  else
432
- msg = 'missing required option :url'
644
+ msg = "missing required option :url"
433
645
  raise ArgumentError, msg
434
646
  end
435
647
  end
436
648
 
437
649
  def downcase_headers(headers)
438
- (headers || {}).to_hash.transform_keys(&:downcase)
439
- end
440
-
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
456
- else
457
- OpenSSL::Digest::SHA256.hexdigest(value)
650
+ (headers || {}).to_hash.inject({}) do |hash, (key, value)|
651
+ hash[key.downcase] = value
652
+ hash
458
653
  end
459
654
  end
460
655
 
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
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
465
660
  else
466
- "#{uri.host}:#{uri.port}"
661
+ msg = "expected :expires_in to be a number of seconds"
662
+ raise ArgumentError, msg
467
663
  end
468
664
  end
469
665
 
470
- # Used only for event signing
471
- def credential_scope(date)
472
- [
473
- date,
474
- @region,
475
- @service,
476
- 'aws4_request',
477
- ].join('/')
666
+ def uri_escape(string)
667
+ self.class.uri_escape(string)
478
668
  end
479
669
 
480
- # Used only for event signing
481
- def hmac(key, value)
482
- OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha256'), key, value)
670
+ def uri_escape_path(string)
671
+ self.class.uri_escape_path(string)
483
672
  end
484
673
 
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")
674
+
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
506
683
  end
507
684
 
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)
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?
523
694
  end
524
695
 
525
696
  class << self
@@ -537,8 +708,8 @@ module Aws
537
708
  CGI.escape(string.encode('UTF-8')).gsub('+', '%20').gsub('%7E', '~')
538
709
  end
539
710
  end
711
+
540
712
  end
541
713
  end
542
714
  end
543
715
  end
544
-
data/lib/aws-sigv4.rb CHANGED
@@ -1,13 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'aws-crt'
4
3
  require_relative 'aws-sigv4/credentials'
5
4
  require_relative 'aws-sigv4/errors'
6
5
  require_relative 'aws-sigv4/signature'
7
6
  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,29 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: aws-sigv4
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.4.0.crt
4
+ version: 1.4.0
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'
27
13
  - !ruby/object:Gem::Dependency
28
14
  name: aws-eventstream
29
15
  requirement: !ruby/object:Gem::Requirement
@@ -44,22 +30,9 @@ dependencies:
44
30
  - - ">="
45
31
  - !ruby/object:Gem::Version
46
32
  version: 1.0.2
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:
33
+ description: Amazon Web Services Signature Version 4 signing library. Generates sigv4
34
+ signature for HTTP requests.
35
+ email:
63
36
  executables: []
64
37
  extensions: []
65
38
  extra_rdoc_files: []
@@ -70,13 +43,16 @@ files:
70
43
  - lib/aws-sigv4.rb
71
44
  - lib/aws-sigv4/credentials.rb
72
45
  - lib/aws-sigv4/errors.rb
46
+ - lib/aws-sigv4/request.rb
73
47
  - lib/aws-sigv4/signature.rb
74
48
  - lib/aws-sigv4/signer.rb
75
- homepage: https://github.com/awslabs/aws-crt-ruby
49
+ homepage: https://github.com/aws/aws-sdk-ruby
76
50
  licenses:
77
51
  - Apache-2.0
78
- metadata: {}
79
- post_install_message:
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:
80
56
  rdoc_options: []
81
57
  require_paths:
82
58
  - lib
@@ -84,15 +60,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
84
60
  requirements:
85
61
  - - ">="
86
62
  - !ruby/object:Gem::Version
87
- version: '2.5'
63
+ version: '2.3'
88
64
  required_rubygems_version: !ruby/object:Gem::Requirement
89
65
  requirements:
90
- - - ">"
66
+ - - ">="
91
67
  - !ruby/object:Gem::Version
92
- version: 1.3.1
68
+ version: '0'
93
69
  requirements: []
94
70
  rubygems_version: 3.1.6
95
- signing_key:
71
+ signing_key:
96
72
  specification_version: 4
97
- summary: AWS SDK for Ruby - Common Runtime (CRT) based Signer
73
+ summary: AWS Signature Version 4 library.
98
74
  test_files: []