aws-sigv4 1.4.0 → 1.8.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: 46dbb72f9e31ff1022703f91ae7e1d2cfe78c78629e682fe99468933704aff62
4
- data.tar.gz: 963db4a8f39031b64398dea0d2ebc7167af20e79446e23bfd270c6cb85d4738f
3
+ metadata.gz: 482b4ffa8bd9e9e2d7dab0d61ab15553f0e8d05e1be2923b388157664a47a9fa
4
+ data.tar.gz: c5caa84527ca213826f8c802195430caacb25750530609f1b8d7267810808574
5
5
  SHA512:
6
- metadata.gz: '0142942e58db9971d8ceaa2aeb7c97d1cd58bdba463366fd06831b1769a3c124f6e1f1b082c4d1b5917f794ab70556f6bd78dbb67824a35e74ccbfc5bdaafa25'
7
- data.tar.gz: aca23ad7a8a98f24abdbbaa2afe9cde1430a7a10f627b63d7da56939665056ccc7a04226dc14e3793e3848032291aba990fc10576369f3974911593566ea9262
6
+ metadata.gz: c16c5df7f8c6ca10cf073c25984506ce938f26823f99d82813b5bde3fb283d23a3c480adec2945b98975946a7b32efe57a0058cd65bb383606c5ee5228711381
7
+ data.tar.gz: b72ea1894eb1c419179325f8e715fb454ab02ae2d1ac243b90ed2f4032c0fd966ba157287a12009587908d0a6a2f62261c78e319a230196792b6ec4206a20718
data/CHANGELOG.md CHANGED
@@ -1,6 +1,41 @@
1
1
  Unreleased Changes
2
2
  ------------------
3
3
 
4
+ 1.8.0 (2023-11-28)
5
+ ------------------
6
+
7
+ * Feature - Support `sigv4-s3express` signing algorithm.
8
+
9
+ 1.7.0 (2023-11-22)
10
+ ------------------
11
+
12
+ * Feature - AWS SDK for Ruby no longer supports Ruby runtime versions 2.3 and 2.4.
13
+
14
+ 1.6.1 (2023-10-25)
15
+ ------------------
16
+
17
+ * Issue - (Static Stability) use provided `expires_in` in presigned url when credentials are expired.
18
+
19
+ 1.6.0 (2023-06-28)
20
+ ------------------
21
+
22
+ * Feature - Select the minimum expiration time for presigned urls between the expiration time option and the credential expiration time.
23
+
24
+ 1.5.2 (2022-09-30)
25
+ ------------------
26
+
27
+ * Issue - Fix an issue where quoted strings with multiple spaces are not trimmed. (#2758)
28
+
29
+ 1.5.1 (2022-07-19)
30
+ ------------------
31
+
32
+ * Issue - Fix performance regression when checking if `aws-crt` is available. (#2729)
33
+
34
+ 1.5.0 (2022-04-20)
35
+ ------------------
36
+
37
+ * Feature - Use CRT based signers if `aws-crt` is available - provides support for `sigv4a`.
38
+
4
39
  1.4.0 (2021-09-02)
5
40
  ------------------
6
41
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.4.0
1
+ 1.8.0
@@ -32,6 +32,8 @@ module Aws
32
32
  # @return [String] For debugging purposes.
33
33
  attr_accessor :content_sha256
34
34
 
35
+ # @return [Hash] Internal data for debugging purposes.
36
+ attr_accessor :extra
35
37
  end
36
38
  end
37
39
  end
@@ -74,6 +74,14 @@ module Aws
74
74
  #
75
75
  class Signer
76
76
 
77
+ @@use_crt =
78
+ begin
79
+ require 'aws-crt'
80
+ true
81
+ rescue LoadError
82
+ false
83
+ end
84
+
77
85
  # @overload initialize(service:, region:, access_key_id:, secret_access_key:, session_token:nil, **options)
78
86
  # @param [String] :service The service signing name, e.g. 's3'.
79
87
  # @param [String] :region The region name, e.g. 'us-east-1'.
@@ -118,6 +126,18 @@ module Aws
118
126
  # headers. This is required for AWS Glacier, and optional for
119
127
  # every other AWS service as of late 2016.
120
128
  #
129
+ # @option options [Symbol] :signing_algorithm (:sigv4) The
130
+ # algorithm to use for signing. :sigv4a is only supported when
131
+ # `aws-crt` is available.
132
+ #
133
+ # @option options [Boolean] :omit_session_token (false)
134
+ # (Supported only when `aws-crt` is available) If `true`,
135
+ # then security token is added to the final signing result,
136
+ # but is treated as "unsigned" and does not contribute
137
+ # to the authorization signature.
138
+ #
139
+ # @option options [Boolean] :normalize_path (true) (Supported only when `aws-crt` is available)
140
+ # When `true`, the uri paths will be normalized when building the canonical request
121
141
  def initialize(options = {})
122
142
  @service = extract_service(options)
123
143
  @region = extract_region(options)
@@ -126,15 +146,24 @@ module Aws
126
146
  @unsigned_headers << 'authorization'
127
147
  @unsigned_headers << 'x-amzn-trace-id'
128
148
  @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
149
+ @uri_escape_path = options.fetch(:uri_escape_path, true)
150
+ @apply_checksum_header = options.fetch(:apply_checksum_header, true)
151
+ @signing_algorithm = options.fetch(:signing_algorithm, :sigv4)
152
+ @normalize_path = options.fetch(:normalize_path, true)
153
+ @omit_session_token = options.fetch(:omit_session_token, false)
132
154
 
133
- if options[:signing_algorithm] == :sigv4a
155
+ if @signing_algorithm == :sigv4a && !Signer.use_crt?
134
156
  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.'\
157
+ ' request with sigv4a which requires the `aws-crt` gem.'\
136
158
  ' Please install the gem or add it to your gemfile.'
137
159
  end
160
+
161
+ if @signing_algorithm == 'sigv4-s3express'.to_sym &&
162
+ Signer.use_crt? && Aws::Crt::GEM_VERSION <= '0.1.9'
163
+ raise ArgumentError,
164
+ 'This version of aws-crt does not support S3 Express. Please
165
+ update this gem to at least version 0.2.0.'
166
+ end
138
167
  end
139
168
 
140
169
  # @return [String]
@@ -211,7 +240,9 @@ module Aws
211
240
  #
212
241
  def sign_request(request)
213
242
 
214
- creds = fetch_credentials
243
+ return crt_sign_request(request) if Signer.use_crt?
244
+
245
+ creds, _ = fetch_credentials
215
246
 
216
247
  http_method = extract_http_method(request)
217
248
  url = extract_url(request)
@@ -227,7 +258,14 @@ module Aws
227
258
  sigv4_headers = {}
228
259
  sigv4_headers['host'] = headers['host'] || host(url)
229
260
  sigv4_headers['x-amz-date'] = datetime
230
- sigv4_headers['x-amz-security-token'] = creds.session_token if creds.session_token
261
+ if creds.session_token
262
+ if @signing_algorithm == 'sigv4-s3express'.to_sym
263
+ sigv4_headers['x-amz-s3session-token'] = creds.session_token
264
+ else
265
+ sigv4_headers['x-amz-security-token'] = creds.session_token
266
+ end
267
+ end
268
+
231
269
  sigv4_headers['x-amz-content-sha256'] ||= content_sha256 if @apply_checksum_header
232
270
 
233
271
  headers = headers.merge(sigv4_headers) # merge so we do not modify given headers hash
@@ -289,7 +327,8 @@ module Aws
289
327
  # signature value (a binary string) used at ':chunk-signature' needs to converted to
290
328
  # hex-encoded string using #unpack
291
329
  def sign_event(prior_signature, payload, encoder)
292
- creds = fetch_credentials
330
+ # Note: CRT does not currently provide event stream signing, so we always use the ruby implementation.
331
+ creds, _ = fetch_credentials
293
332
  time = Time.now
294
333
  headers = {}
295
334
 
@@ -376,7 +415,9 @@ module Aws
376
415
  #
377
416
  def presign_url(options)
378
417
 
379
- creds = fetch_credentials
418
+ return crt_presign_url(options) if Signer.use_crt?
419
+
420
+ creds, expiration = fetch_credentials
380
421
 
381
422
  http_method = extract_http_method(options)
382
423
  url = extract_url(options)
@@ -396,8 +437,14 @@ module Aws
396
437
  params['X-Amz-Algorithm'] = 'AWS4-HMAC-SHA256'
397
438
  params['X-Amz-Credential'] = credential(creds, date)
398
439
  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
440
+ params['X-Amz-Expires'] = presigned_url_expiration(options, expiration, Time.strptime(datetime, "%Y%m%dT%H%M%S%Z")).to_s
441
+ if creds.session_token
442
+ if @signing_algorithm == 'sigv4-s3express'.to_sym
443
+ params['X-Amz-S3session-Token'] = creds.session_token
444
+ else
445
+ params['X-Amz-Security-Token'] = creds.session_token
446
+ end
447
+ end
401
448
  params['X-Amz-SignedHeaders'] = signed_headers(headers)
402
449
 
403
450
  params = params.map do |key, value|
@@ -499,7 +546,6 @@ module Aws
499
546
  hmac(k_credentials, string_to_sign)
500
547
  end
501
548
 
502
-
503
549
  def path(url)
504
550
  path = url.path
505
551
  path = '/' if path == ''
@@ -562,7 +608,7 @@ module Aws
562
608
  end
563
609
 
564
610
  def canonical_header_value(value)
565
- value.match(/^".*"$/) ? value : value.gsub(/\s+/, ' ').strip
611
+ value.gsub(/\s+/, ' ').strip
566
612
  end
567
613
 
568
614
  def host(uri)
@@ -655,8 +701,8 @@ module Aws
655
701
 
656
702
  def extract_expires_in(options)
657
703
  case options[:expires_in]
658
- when nil then 900.to_s
659
- when Integer then options[:expires_in].to_s
704
+ when nil then 900
705
+ when Integer then options[:expires_in]
660
706
  else
661
707
  msg = "expected :expires_in to be a number of seconds"
662
708
  raise ArgumentError, msg
@@ -671,11 +717,14 @@ module Aws
671
717
  self.class.uri_escape_path(string)
672
718
  end
673
719
 
674
-
675
720
  def fetch_credentials
676
721
  credentials = @credentials_provider.credentials
677
722
  if credentials_set?(credentials)
678
- credentials
723
+ expiration = nil
724
+ if @credentials_provider.respond_to?(:expiration)
725
+ expiration = @credentials_provider.expiration
726
+ end
727
+ [credentials, expiration]
679
728
  else
680
729
  raise Errors::MissingCredentialsError,
681
730
  'unable to sign request without credentials set'
@@ -693,8 +742,146 @@ module Aws
693
742
  !credentials.secret_access_key.empty?
694
743
  end
695
744
 
745
+ def presigned_url_expiration(options, expiration, datetime)
746
+ expires_in = extract_expires_in(options)
747
+ return expires_in unless expiration
748
+
749
+ expiration_seconds = (expiration - datetime).to_i
750
+ # In the static stability case, credentials may expire in the past
751
+ # but still be valid. For those cases, use the user configured
752
+ # expires_in and ingore expiration.
753
+ if expiration_seconds <= 0
754
+ expires_in
755
+ else
756
+ [expires_in, expiration_seconds].min
757
+ end
758
+ end
759
+
760
+ ### CRT Code
761
+
762
+ # the credentials used by CRT must be a
763
+ # CRT StaticCredentialsProvider object
764
+ def crt_fetch_credentials
765
+ creds, expiration = fetch_credentials
766
+ crt_creds = Aws::Crt::Auth::StaticCredentialsProvider.new(
767
+ creds.access_key_id,
768
+ creds.secret_access_key,
769
+ creds.session_token
770
+ )
771
+ [crt_creds, expiration]
772
+ end
773
+
774
+ def crt_sign_request(request)
775
+ creds, _ = crt_fetch_credentials
776
+ http_method = extract_http_method(request)
777
+ url = extract_url(request)
778
+ headers = downcase_headers(request[:headers])
779
+
780
+ datetime =
781
+ if headers.include? 'x-amz-date'
782
+ Time.parse(headers.delete('x-amz-date'))
783
+ end
784
+
785
+ content_sha256 = headers.delete('x-amz-content-sha256')
786
+ content_sha256 ||= sha256_hexdigest(request[:body] || '')
787
+
788
+ sigv4_headers = {}
789
+ sigv4_headers['host'] = headers['host'] || host(url)
790
+
791
+ # Modify the user-agent to add usage of crt-signer
792
+ # This should be temporary during developer preview only
793
+ if headers.include? 'user-agent'
794
+ headers['user-agent'] = "#{headers['user-agent']} crt-signer/#{@signing_algorithm}/#{Aws::Sigv4::VERSION}"
795
+ sigv4_headers['user-agent'] = headers['user-agent']
796
+ end
797
+
798
+ headers = headers.merge(sigv4_headers) # merge so we do not modify given headers hash
799
+
800
+ config = Aws::Crt::Auth::SigningConfig.new(
801
+ algorithm: @signing_algorithm,
802
+ signature_type: :http_request_headers,
803
+ region: @region,
804
+ service: @service,
805
+ date: datetime,
806
+ signed_body_value: content_sha256,
807
+ signed_body_header_type: @apply_checksum_header ?
808
+ :sbht_content_sha256 : :sbht_none,
809
+ credentials: creds,
810
+ unsigned_headers: @unsigned_headers,
811
+ use_double_uri_encode: @uri_escape_path,
812
+ should_normalize_uri_path: @normalize_path,
813
+ omit_session_token: @omit_session_token
814
+ )
815
+ http_request = Aws::Crt::Http::Message.new(
816
+ http_method, url.to_s, headers
817
+ )
818
+ signable = Aws::Crt::Auth::Signable.new(http_request)
819
+
820
+ signing_result = Aws::Crt::Auth::Signer.sign_request(config, signable)
821
+
822
+ Signature.new(
823
+ headers: sigv4_headers.merge(
824
+ downcase_headers(signing_result[:headers])
825
+ ),
826
+ string_to_sign: 'CRT_INTERNAL',
827
+ canonical_request: 'CRT_INTERNAL',
828
+ content_sha256: content_sha256,
829
+ extra: {config: config, signable: signable}
830
+ )
831
+ end
832
+
833
+ def crt_presign_url(options)
834
+ creds, expiration = crt_fetch_credentials
835
+
836
+ http_method = extract_http_method(options)
837
+ url = extract_url(options)
838
+ headers = downcase_headers(options[:headers])
839
+ headers['host'] ||= host(url)
840
+
841
+ datetime = Time.strptime(headers.delete('x-amz-date'), "%Y%m%dT%H%M%S%Z") if headers['x-amz-date']
842
+ datetime ||= (options[:time] || Time.now)
843
+
844
+ content_sha256 = headers.delete('x-amz-content-sha256')
845
+ content_sha256 ||= options[:body_digest]
846
+ content_sha256 ||= sha256_hexdigest(options[:body] || '')
847
+
848
+ config = Aws::Crt::Auth::SigningConfig.new(
849
+ algorithm: @signing_algorithm,
850
+ signature_type: :http_request_query_params,
851
+ region: @region,
852
+ service: @service,
853
+ date: datetime,
854
+ signed_body_value: content_sha256,
855
+ signed_body_header_type: @apply_checksum_header ?
856
+ :sbht_content_sha256 : :sbht_none,
857
+ credentials: creds,
858
+ unsigned_headers: @unsigned_headers,
859
+ use_double_uri_encode: @uri_escape_path,
860
+ should_normalize_uri_path: @normalize_path,
861
+ omit_session_token: @omit_session_token,
862
+ expiration_in_seconds: presigned_url_expiration(options, expiration, datetime)
863
+ )
864
+ http_request = Aws::Crt::Http::Message.new(
865
+ http_method, url.to_s, headers
866
+ )
867
+ signable = Aws::Crt::Auth::Signable.new(http_request)
868
+
869
+ signing_result = Aws::Crt::Auth::Signer.sign_request(config, signable, http_method, url.to_s)
870
+ url = URI.parse(signing_result[:path])
871
+
872
+ if options[:extra] && options[:extra].is_a?(Hash)
873
+ options[:extra][:config] = config
874
+ options[:extra][:signable] = signable
875
+ end
876
+ url
877
+ end
878
+
696
879
  class << self
697
880
 
881
+ def use_crt?
882
+ @@use_crt
883
+ end
884
+
698
885
  # @api private
699
886
  def uri_escape_path(path)
700
887
  path.gsub(/[^\/]+/) { |part| uri_escape(part) }
data/lib/aws-sigv4.rb CHANGED
@@ -4,3 +4,9 @@ require_relative 'aws-sigv4/credentials'
4
4
  require_relative 'aws-sigv4/errors'
5
5
  require_relative 'aws-sigv4/signature'
6
6
  require_relative 'aws-sigv4/signer'
7
+
8
+ module Aws
9
+ module Sigv4
10
+ VERSION = File.read(File.expand_path('../VERSION', __dir__)).strip
11
+ end
12
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: aws-sigv4
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.4.0
4
+ version: 1.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Amazon Web Services
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-09-02 00:00:00.000000000 Z
11
+ date: 2023-11-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: aws-eventstream
@@ -60,7 +60,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
60
60
  requirements:
61
61
  - - ">="
62
62
  - !ruby/object:Gem::Version
63
- version: '2.3'
63
+ version: '2.5'
64
64
  required_rubygems_version: !ruby/object:Gem::Requirement
65
65
  requirements:
66
66
  - - ">="