aws-sigv4 1.6.0 → 1.12.1
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 +4 -4
- data/CHANGELOG.md +50 -0
- data/VERSION +1 -1
- data/lib/aws-sigv4/asymmetric_credentials.rb +99 -0
- data/lib/aws-sigv4/request.rb +3 -3
- data/lib/aws-sigv4/signature.rb +3 -0
- data/lib/aws-sigv4/signer.rb +113 -169
- data/lib/aws-sigv4.rb +1 -0
- metadata +5 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 58318b8045877e5842f7c0793c856e30b6bba8425988a4929bca3aaaf64fcc06
|
4
|
+
data.tar.gz: c9de352d0dee1c35637e86d1d4a3a8817d3ee6a4c3595f8e86436cf216d0a35d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b22918d3e6a784f4198b9b3583b6ce34e492819ec401862333d29f7c456402fe10718f7e9f30479d5097b89b19d8030400647572ee7204b8729093b16290f3f2
|
7
|
+
data.tar.gz: c15f22f66888fa2bbe1822bd833c2bc87c216b3c082ba1961682e0dcd2f5c0f427c2126ffb04354a3ba2b47c5a3fb375ad2381be543021b089687f589a4565fd
|
data/CHANGELOG.md
CHANGED
@@ -1,6 +1,56 @@
|
|
1
1
|
Unreleased Changes
|
2
2
|
------------------
|
3
3
|
|
4
|
+
1.12.1 (2025-06-10)
|
5
|
+
------------------
|
6
|
+
|
7
|
+
* Issue - Only load required `cgi` modules for Ruby 3.5.
|
8
|
+
|
9
|
+
1.12.0 (2025-06-02)
|
10
|
+
------------------
|
11
|
+
|
12
|
+
* Feature - AWS SDK for Ruby no longer supports Ruby runtime versions 2.5 and 2.6.
|
13
|
+
|
14
|
+
1.11.0 (2025-01-10)
|
15
|
+
------------------
|
16
|
+
|
17
|
+
* Feature - Add RBS signature files to support static type checking
|
18
|
+
|
19
|
+
1.10.1 (2024-10-21)
|
20
|
+
------------------
|
21
|
+
|
22
|
+
* Issue - Fix sigv4a signing issue with derive_asymmetric_key for certain credentials.
|
23
|
+
|
24
|
+
1.10.0 (2024-09-17)
|
25
|
+
------------------
|
26
|
+
|
27
|
+
* Feature - Remove CRT `sigv4a` signing capability.
|
28
|
+
|
29
|
+
1.9.1 (2024-07-29)
|
30
|
+
------------------
|
31
|
+
|
32
|
+
* Issue - Add missing require of `pathname` to `Signer`.
|
33
|
+
|
34
|
+
1.9.0 (2024-07-23)
|
35
|
+
------------------
|
36
|
+
|
37
|
+
* Feature - Support `sigv4a` signing algorithm without `aws-crt`.
|
38
|
+
|
39
|
+
1.8.0 (2023-11-28)
|
40
|
+
------------------
|
41
|
+
|
42
|
+
* Feature - Support `sigv4-s3express` signing algorithm.
|
43
|
+
|
44
|
+
1.7.0 (2023-11-22)
|
45
|
+
------------------
|
46
|
+
|
47
|
+
* Feature - AWS SDK for Ruby no longer supports Ruby runtime versions 2.3 and 2.4.
|
48
|
+
|
49
|
+
1.6.1 (2023-10-25)
|
50
|
+
------------------
|
51
|
+
|
52
|
+
* Issue - (Static Stability) use provided `expires_in` in presigned url when credentials are expired.
|
53
|
+
|
4
54
|
1.6.0 (2023-06-28)
|
5
55
|
------------------
|
6
56
|
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
1.
|
1
|
+
1.12.1
|
@@ -0,0 +1,99 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Aws
|
4
|
+
module Sigv4
|
5
|
+
# To make it easier to support mixed mode, we have created an asymmetric
|
6
|
+
# key derivation mechanism. This module derives
|
7
|
+
# asymmetric keys from the current secret for use with
|
8
|
+
# Asymmetric signatures.
|
9
|
+
# @api private
|
10
|
+
module AsymmetricCredentials
|
11
|
+
|
12
|
+
N_MINUS_2 = 0xFFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551 - 2
|
13
|
+
|
14
|
+
# @return [OpenSSL::PKey::EC, Hash]
|
15
|
+
def self.derive_asymmetric_key(access_key_id, secret_access_key)
|
16
|
+
check_openssl_support!
|
17
|
+
label = 'AWS4-ECDSA-P256-SHA256'
|
18
|
+
bit_len = 256
|
19
|
+
counter = 0x1
|
20
|
+
input_key = "AWS4A#{secret_access_key}"
|
21
|
+
d = 0 # d will end up being the private key
|
22
|
+
while true do
|
23
|
+
|
24
|
+
kdf_context = access_key_id.unpack('C*') + [counter].pack('C').unpack('C') #1 byte for counter
|
25
|
+
input = label.unpack('C*') + [0x00] + kdf_context + [bit_len].pack('L>').unpack('CCCC') # 4 bytes (change endianess)
|
26
|
+
k0 = OpenSSL::HMAC.digest("SHA256", input_key, ([0, 0, 0, 0x01] + input).pack('C*'))
|
27
|
+
c = be_bytes_to_num( k0.unpack('C*') )
|
28
|
+
if c <= N_MINUS_2
|
29
|
+
d = c + 1
|
30
|
+
break
|
31
|
+
elsif counter > 0xFF
|
32
|
+
raise 'Counter exceeded 1 byte - unable to get asym creds'
|
33
|
+
else
|
34
|
+
counter += 1
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# compute the public key
|
39
|
+
group = OpenSSL::PKey::EC::Group.new('prime256v1')
|
40
|
+
public_key = group.generator.mul(d)
|
41
|
+
|
42
|
+
ec = generate_ec(public_key, d)
|
43
|
+
|
44
|
+
# pk_x and pk_y are not needed for signature, but useful in verification/testing
|
45
|
+
pk_b = public_key.to_octet_string(:uncompressed).unpack('C*') # 0x04 byte followed by 2 32-byte integers
|
46
|
+
pk_x = be_bytes_to_num(pk_b[1,32])
|
47
|
+
pk_y = be_bytes_to_num(pk_b[33,32])
|
48
|
+
[ec, {ec: ec, public_key: public_key, pk_x: pk_x, pk_y: pk_y, d: d}]
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
# @return [Number] The value of the bytes interpreted as a big-endian
|
54
|
+
# unsigned integer.
|
55
|
+
def self.be_bytes_to_num(bytes)
|
56
|
+
x = 0
|
57
|
+
bytes.each { |b| x = (x*256) + b }
|
58
|
+
x
|
59
|
+
end
|
60
|
+
|
61
|
+
# @return [Array] value of the BigNumber as a big-endian unsigned byte array.
|
62
|
+
def self.bn_to_be_bytes(bn)
|
63
|
+
bytes = []
|
64
|
+
while bn > 0
|
65
|
+
bytes << (bn & 0xff)
|
66
|
+
bn = bn >> 8
|
67
|
+
end
|
68
|
+
bytes.reverse
|
69
|
+
end
|
70
|
+
|
71
|
+
# Prior to openssl3 we could directly set public and private key on EC
|
72
|
+
# However, openssl3 deprecated those methods and we must now construct
|
73
|
+
# a der with the keys and load the EC from it.
|
74
|
+
def self.generate_ec(public_key, d)
|
75
|
+
# format reversed from: OpenSSL::ASN1.decode_all(OpenSSL::PKey::EC.new.to_der)
|
76
|
+
asn1 = OpenSSL::ASN1::Sequence([
|
77
|
+
OpenSSL::ASN1::Integer(OpenSSL::BN.new(1)),
|
78
|
+
OpenSSL::ASN1::OctetString(bn_to_be_bytes(d).pack('C*')),
|
79
|
+
OpenSSL::ASN1::ASN1Data.new([OpenSSL::ASN1::ObjectId("prime256v1")], 0, :CONTEXT_SPECIFIC),
|
80
|
+
OpenSSL::ASN1::ASN1Data.new(
|
81
|
+
[OpenSSL::ASN1::BitString(public_key.to_octet_string(:uncompressed))],
|
82
|
+
1, :CONTEXT_SPECIFIC
|
83
|
+
)
|
84
|
+
])
|
85
|
+
OpenSSL::PKey::EC.new(asn1.to_der)
|
86
|
+
end
|
87
|
+
|
88
|
+
def self.check_openssl_support!
|
89
|
+
return true unless defined?(JRUBY_VERSION)
|
90
|
+
|
91
|
+
# See: https://github.com/jruby/jruby-openssl/issues/306
|
92
|
+
# JRuby-openssl < 0.15 does not support OpenSSL::PKey::EC::Point#mul
|
93
|
+
return true if OpenSSL::PKey::EC::Point.instance_methods.include?(:mul)
|
94
|
+
|
95
|
+
raise 'Sigv4a Asymmetric Credential derivation requires jruby-openssl >= 0.15'
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
data/lib/aws-sigv4/request.rb
CHANGED
@@ -7,7 +7,7 @@ module Aws
|
|
7
7
|
class Request
|
8
8
|
|
9
9
|
# @option options [required, String] :http_method
|
10
|
-
# @option options [required,
|
10
|
+
# @option options [required, String, URI::HTTP, URI::HTTPS] :endpoint
|
11
11
|
# @option options [Hash<String,String>] :headers ({})
|
12
12
|
# @option options [String, IO] :body ('')
|
13
13
|
def initialize(options = {})
|
@@ -30,12 +30,12 @@ module Aws
|
|
30
30
|
@http_method
|
31
31
|
end
|
32
32
|
|
33
|
-
# @param [String, HTTP
|
33
|
+
# @param [String, URI::HTTP, URI::HTTPS] endpoint
|
34
34
|
def endpoint=(endpoint)
|
35
35
|
@endpoint = URI.parse(endpoint.to_s)
|
36
36
|
end
|
37
37
|
|
38
|
-
# @return [HTTP
|
38
|
+
# @return [URI::HTTP, URI::HTTPS]
|
39
39
|
def endpoint
|
40
40
|
@endpoint
|
41
41
|
end
|
data/lib/aws-sigv4/signature.rb
CHANGED
@@ -32,6 +32,9 @@ module Aws
|
|
32
32
|
# @return [String] For debugging purposes.
|
33
33
|
attr_accessor :content_sha256
|
34
34
|
|
35
|
+
# @return [String] For debugging purposes.
|
36
|
+
attr_accessor :signature
|
37
|
+
|
35
38
|
# @return [Hash] Internal data for debugging purposes.
|
36
39
|
attr_accessor :extra
|
37
40
|
end
|
data/lib/aws-sigv4/signer.rb
CHANGED
@@ -5,7 +5,9 @@ require 'tempfile'
|
|
5
5
|
require 'time'
|
6
6
|
require 'uri'
|
7
7
|
require 'set'
|
8
|
-
require
|
8
|
+
require "cgi/escape"
|
9
|
+
require "cgi/util" if RUBY_VERSION < "3.5"
|
10
|
+
require 'pathname'
|
9
11
|
require 'aws-eventstream'
|
10
12
|
|
11
13
|
module Aws
|
@@ -73,25 +75,18 @@ module Aws
|
|
73
75
|
# and `#session_token`.
|
74
76
|
#
|
75
77
|
class Signer
|
76
|
-
|
77
|
-
@@use_crt =
|
78
|
-
begin
|
79
|
-
require 'aws-crt'
|
80
|
-
true
|
81
|
-
rescue LoadError
|
82
|
-
false
|
83
|
-
end
|
84
|
-
|
85
78
|
# @overload initialize(service:, region:, access_key_id:, secret_access_key:, session_token:nil, **options)
|
86
79
|
# @param [String] :service The service signing name, e.g. 's3'.
|
87
|
-
# @param [String] :region The region name, e.g. 'us-east-1'.
|
80
|
+
# @param [String] :region The region name, e.g. 'us-east-1'. When signing
|
81
|
+
# with sigv4a, this should be a comma separated list of regions.
|
88
82
|
# @param [String] :access_key_id
|
89
83
|
# @param [String] :secret_access_key
|
90
84
|
# @param [String] :session_token (nil)
|
91
85
|
#
|
92
86
|
# @overload initialize(service:, region:, credentials:, **options)
|
93
87
|
# @param [String] :service The service signing name, e.g. 's3'.
|
94
|
-
# @param [String] :region The region name, e.g. 'us-east-1'.
|
88
|
+
# @param [String] :region The region name, e.g. 'us-east-1'. When signing
|
89
|
+
# with sigv4a, this should be a comma separated list of regions.
|
95
90
|
# @param [Credentials] :credentials Any object that responds to the following
|
96
91
|
# methods:
|
97
92
|
#
|
@@ -102,7 +97,8 @@ module Aws
|
|
102
97
|
#
|
103
98
|
# @overload initialize(service:, region:, credentials_provider:, **options)
|
104
99
|
# @param [String] :service The service signing name, e.g. 's3'.
|
105
|
-
# @param [String] :region The region name, e.g. 'us-east-1'.
|
100
|
+
# @param [String] :region The region name, e.g. 'us-east-1'. When signing
|
101
|
+
# with sigv4a, this should be a comma separated list of regions.
|
106
102
|
# @param [#credentials] :credentials_provider An object that responds
|
107
103
|
# to `#credentials`, returning an object that responds to the following
|
108
104
|
# methods:
|
@@ -127,8 +123,7 @@ module Aws
|
|
127
123
|
# every other AWS service as of late 2016.
|
128
124
|
#
|
129
125
|
# @option options [Symbol] :signing_algorithm (:sigv4) The
|
130
|
-
# algorithm to use for signing.
|
131
|
-
# `aws-crt` is available.
|
126
|
+
# algorithm to use for signing.
|
132
127
|
#
|
133
128
|
# @option options [Boolean] :omit_session_token (false)
|
134
129
|
# (Supported only when `aws-crt` is available) If `true`,
|
@@ -136,8 +131,8 @@ module Aws
|
|
136
131
|
# but is treated as "unsigned" and does not contribute
|
137
132
|
# to the authorization signature.
|
138
133
|
#
|
139
|
-
# @option options [Boolean] :normalize_path (true)
|
140
|
-
#
|
134
|
+
# @option options [Boolean] :normalize_path (true) When `true`, the
|
135
|
+
# uri paths will be normalized when building the canonical request.
|
141
136
|
def initialize(options = {})
|
142
137
|
@service = extract_service(options)
|
143
138
|
@region = extract_region(options)
|
@@ -151,12 +146,6 @@ module Aws
|
|
151
146
|
@signing_algorithm = options.fetch(:signing_algorithm, :sigv4)
|
152
147
|
@normalize_path = options.fetch(:normalize_path, true)
|
153
148
|
@omit_session_token = options.fetch(:omit_session_token, false)
|
154
|
-
|
155
|
-
if @signing_algorithm == :sigv4a && !Signer.use_crt?
|
156
|
-
raise ArgumentError, 'You are attempting to sign a' \
|
157
|
-
' request with sigv4a which requires the `aws-crt` gem.'\
|
158
|
-
' Please install the gem or add it to your gemfile.'
|
159
|
-
end
|
160
149
|
end
|
161
150
|
|
162
151
|
# @return [String]
|
@@ -217,7 +206,7 @@ module Aws
|
|
217
206
|
# @option request [required, String] :http_method One of
|
218
207
|
# 'GET', 'HEAD', 'PUT', 'POST', 'PATCH', or 'DELETE'
|
219
208
|
#
|
220
|
-
# @option request [required, String, URI::
|
209
|
+
# @option request [required, String, URI::HTTP, URI::HTTPS] :url
|
221
210
|
# The request URI. Must be a valid HTTP or HTTPS URI.
|
222
211
|
#
|
223
212
|
# @option request [optional, Hash] :headers ({}) A hash of headers
|
@@ -232,13 +221,11 @@ module Aws
|
|
232
221
|
# a `#headers` method. The headers must be applied to your request.
|
233
222
|
#
|
234
223
|
def sign_request(request)
|
235
|
-
|
236
|
-
return crt_sign_request(request) if Signer.use_crt?
|
237
|
-
|
238
224
|
creds, _ = fetch_credentials
|
239
225
|
|
240
226
|
http_method = extract_http_method(request)
|
241
227
|
url = extract_url(request)
|
228
|
+
Signer.normalize_path(url) if @normalize_path
|
242
229
|
headers = downcase_headers(request[:headers])
|
243
230
|
|
244
231
|
datetime = headers['x-amz-date']
|
@@ -251,29 +238,55 @@ module Aws
|
|
251
238
|
sigv4_headers = {}
|
252
239
|
sigv4_headers['host'] = headers['host'] || host(url)
|
253
240
|
sigv4_headers['x-amz-date'] = datetime
|
254
|
-
|
241
|
+
if creds.session_token && !@omit_session_token
|
242
|
+
if @signing_algorithm == 'sigv4-s3express'.to_sym
|
243
|
+
sigv4_headers['x-amz-s3session-token'] = creds.session_token
|
244
|
+
else
|
245
|
+
sigv4_headers['x-amz-security-token'] = creds.session_token
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
255
249
|
sigv4_headers['x-amz-content-sha256'] ||= content_sha256 if @apply_checksum_header
|
256
250
|
|
251
|
+
if @signing_algorithm == :sigv4a && @region && !@region.empty?
|
252
|
+
sigv4_headers['x-amz-region-set'] = @region
|
253
|
+
end
|
257
254
|
headers = headers.merge(sigv4_headers) # merge so we do not modify given headers hash
|
258
255
|
|
256
|
+
algorithm = sts_algorithm
|
257
|
+
|
259
258
|
# compute signature parts
|
260
259
|
creq = canonical_request(http_method, url, headers, content_sha256)
|
261
|
-
sts = string_to_sign(datetime, creq)
|
262
|
-
|
260
|
+
sts = string_to_sign(datetime, creq, algorithm)
|
261
|
+
|
262
|
+
sig =
|
263
|
+
if @signing_algorithm == :sigv4a
|
264
|
+
asymmetric_signature(creds, sts)
|
265
|
+
else
|
266
|
+
signature(creds.secret_access_key, date, sts)
|
267
|
+
end
|
268
|
+
|
269
|
+
algorithm = sts_algorithm
|
263
270
|
|
264
271
|
# apply signature
|
265
272
|
sigv4_headers['authorization'] = [
|
266
|
-
"
|
273
|
+
"#{algorithm} Credential=#{credential(creds, date)}",
|
267
274
|
"SignedHeaders=#{signed_headers(headers)}",
|
268
275
|
"Signature=#{sig}",
|
269
276
|
].join(', ')
|
270
277
|
|
278
|
+
# skip signing the session token, but include it in the headers
|
279
|
+
if creds.session_token && @omit_session_token
|
280
|
+
sigv4_headers['x-amz-security-token'] = creds.session_token
|
281
|
+
end
|
282
|
+
|
271
283
|
# Returning the signature components.
|
272
284
|
Signature.new(
|
273
285
|
headers: sigv4_headers,
|
274
286
|
string_to_sign: sts,
|
275
287
|
canonical_request: creq,
|
276
|
-
content_sha256: content_sha256
|
288
|
+
content_sha256: content_sha256,
|
289
|
+
signature: sig
|
277
290
|
)
|
278
291
|
end
|
279
292
|
|
@@ -313,7 +326,6 @@ module Aws
|
|
313
326
|
# signature value (a binary string) used at ':chunk-signature' needs to converted to
|
314
327
|
# hex-encoded string using #unpack
|
315
328
|
def sign_event(prior_signature, payload, encoder)
|
316
|
-
# Note: CRT does not currently provide event stream signing, so we always use the ruby implementation.
|
317
329
|
creds, _ = fetch_credentials
|
318
330
|
time = Time.now
|
319
331
|
headers = {}
|
@@ -372,7 +384,7 @@ module Aws
|
|
372
384
|
# @option options [required, String] :http_method The HTTP request method,
|
373
385
|
# e.g. 'GET', 'HEAD', 'PUT', 'POST', 'PATCH', or 'DELETE'.
|
374
386
|
#
|
375
|
-
# @option options [required, String,
|
387
|
+
# @option options [required, String, URI::HTTP, URI::HTTPS] :url
|
376
388
|
# The URI to sign.
|
377
389
|
#
|
378
390
|
# @option options [Hash] :headers ({}) Headers that should
|
@@ -400,13 +412,11 @@ module Aws
|
|
400
412
|
# @return [HTTPS::URI, HTTP::URI]
|
401
413
|
#
|
402
414
|
def presign_url(options)
|
403
|
-
|
404
|
-
return crt_presign_url(options) if Signer.use_crt?
|
405
|
-
|
406
415
|
creds, expiration = fetch_credentials
|
407
416
|
|
408
417
|
http_method = extract_http_method(options)
|
409
418
|
url = extract_url(options)
|
419
|
+
Signer.normalize_path(url) if @normalize_path
|
410
420
|
|
411
421
|
headers = downcase_headers(options[:headers])
|
412
422
|
headers['host'] ||= host(url)
|
@@ -419,14 +429,26 @@ module Aws
|
|
419
429
|
content_sha256 ||= options[:body_digest]
|
420
430
|
content_sha256 ||= sha256_hexdigest(options[:body] || '')
|
421
431
|
|
432
|
+
algorithm = sts_algorithm
|
433
|
+
|
422
434
|
params = {}
|
423
|
-
params['X-Amz-Algorithm'] =
|
435
|
+
params['X-Amz-Algorithm'] = algorithm
|
424
436
|
params['X-Amz-Credential'] = credential(creds, date)
|
425
437
|
params['X-Amz-Date'] = datetime
|
426
|
-
params['X-Amz-Expires'] = presigned_url_expiration(options, expiration).to_s
|
427
|
-
|
438
|
+
params['X-Amz-Expires'] = presigned_url_expiration(options, expiration, Time.strptime(datetime, "%Y%m%dT%H%M%S%Z")).to_s
|
439
|
+
if creds.session_token
|
440
|
+
if @signing_algorithm == 'sigv4-s3express'.to_sym
|
441
|
+
params['X-Amz-S3session-Token'] = creds.session_token
|
442
|
+
else
|
443
|
+
params['X-Amz-Security-Token'] = creds.session_token
|
444
|
+
end
|
445
|
+
end
|
428
446
|
params['X-Amz-SignedHeaders'] = signed_headers(headers)
|
429
447
|
|
448
|
+
if @signing_algorithm == :sigv4a && @region
|
449
|
+
params['X-Amz-Region-Set'] = @region
|
450
|
+
end
|
451
|
+
|
430
452
|
params = params.map do |key, value|
|
431
453
|
"#{uri_escape(key)}=#{uri_escape(value)}"
|
432
454
|
end.join('&')
|
@@ -438,13 +460,23 @@ module Aws
|
|
438
460
|
end
|
439
461
|
|
440
462
|
creq = canonical_request(http_method, url, headers, content_sha256)
|
441
|
-
sts = string_to_sign(datetime, creq)
|
442
|
-
|
463
|
+
sts = string_to_sign(datetime, creq, algorithm)
|
464
|
+
signature =
|
465
|
+
if @signing_algorithm == :sigv4a
|
466
|
+
asymmetric_signature(creds, sts)
|
467
|
+
else
|
468
|
+
signature(creds.secret_access_key, date, sts)
|
469
|
+
end
|
470
|
+
url.query += '&X-Amz-Signature=' + signature
|
443
471
|
url
|
444
472
|
end
|
445
473
|
|
446
474
|
private
|
447
475
|
|
476
|
+
def sts_algorithm
|
477
|
+
@signing_algorithm == :sigv4a ? 'AWS4-ECDSA-P256-SHA256' : 'AWS4-HMAC-SHA256'
|
478
|
+
end
|
479
|
+
|
448
480
|
def canonical_request(http_method, url, headers, content_sha256)
|
449
481
|
[
|
450
482
|
http_method,
|
@@ -456,9 +488,9 @@ module Aws
|
|
456
488
|
].join("\n")
|
457
489
|
end
|
458
490
|
|
459
|
-
def string_to_sign(datetime, canonical_request)
|
491
|
+
def string_to_sign(datetime, canonical_request, algorithm)
|
460
492
|
[
|
461
|
-
|
493
|
+
algorithm,
|
462
494
|
datetime,
|
463
495
|
credential_scope(datetime[0,8]),
|
464
496
|
sha256_hexdigest(canonical_request),
|
@@ -491,10 +523,10 @@ module Aws
|
|
491
523
|
def credential_scope(date)
|
492
524
|
[
|
493
525
|
date,
|
494
|
-
@region,
|
526
|
+
(@region unless @signing_algorithm == :sigv4a),
|
495
527
|
@service,
|
496
|
-
'aws4_request'
|
497
|
-
].join('/')
|
528
|
+
'aws4_request'
|
529
|
+
].compact.join('/')
|
498
530
|
end
|
499
531
|
|
500
532
|
def credential(credentials, date)
|
@@ -509,6 +541,16 @@ module Aws
|
|
509
541
|
hexhmac(k_credentials, string_to_sign)
|
510
542
|
end
|
511
543
|
|
544
|
+
def asymmetric_signature(creds, string_to_sign)
|
545
|
+
ec, _ = Aws::Sigv4::AsymmetricCredentials.derive_asymmetric_key(
|
546
|
+
creds.access_key_id, creds.secret_access_key
|
547
|
+
)
|
548
|
+
sts_digest = OpenSSL::Digest::SHA256.digest(string_to_sign)
|
549
|
+
s = ec.dsa_sign_asn1(sts_digest)
|
550
|
+
|
551
|
+
Digest.hexencode(s)
|
552
|
+
end
|
553
|
+
|
512
554
|
# Comparing to original signature v4 algorithm,
|
513
555
|
# returned signature is a binary string instread of
|
514
556
|
# hex-encoded string. (Since ':chunk-signature' requires
|
@@ -722,137 +764,27 @@ module Aws
|
|
722
764
|
!credentials.secret_access_key.empty?
|
723
765
|
end
|
724
766
|
|
725
|
-
def presigned_url_expiration(options, expiration)
|
767
|
+
def presigned_url_expiration(options, expiration, datetime)
|
726
768
|
expires_in = extract_expires_in(options)
|
727
769
|
return expires_in unless expiration
|
728
770
|
|
729
|
-
expiration_seconds = (expiration -
|
730
|
-
|
731
|
-
|
732
|
-
|
733
|
-
|
734
|
-
|
735
|
-
|
736
|
-
|
737
|
-
def crt_fetch_credentials
|
738
|
-
creds, expiration = fetch_credentials
|
739
|
-
crt_creds = Aws::Crt::Auth::StaticCredentialsProvider.new(
|
740
|
-
creds.access_key_id,
|
741
|
-
creds.secret_access_key,
|
742
|
-
creds.session_token
|
743
|
-
)
|
744
|
-
[crt_creds, expiration]
|
745
|
-
end
|
746
|
-
|
747
|
-
def crt_sign_request(request)
|
748
|
-
creds, _ = crt_fetch_credentials
|
749
|
-
http_method = extract_http_method(request)
|
750
|
-
url = extract_url(request)
|
751
|
-
headers = downcase_headers(request[:headers])
|
752
|
-
|
753
|
-
datetime =
|
754
|
-
if headers.include? 'x-amz-date'
|
755
|
-
Time.parse(headers.delete('x-amz-date'))
|
756
|
-
end
|
757
|
-
|
758
|
-
content_sha256 = headers.delete('x-amz-content-sha256')
|
759
|
-
content_sha256 ||= sha256_hexdigest(request[:body] || '')
|
760
|
-
|
761
|
-
sigv4_headers = {}
|
762
|
-
sigv4_headers['host'] = headers['host'] || host(url)
|
763
|
-
|
764
|
-
# Modify the user-agent to add usage of crt-signer
|
765
|
-
# This should be temporary during developer preview only
|
766
|
-
if headers.include? 'user-agent'
|
767
|
-
headers['user-agent'] = "#{headers['user-agent']} crt-signer/#{@signing_algorithm}/#{Aws::Sigv4::VERSION}"
|
768
|
-
sigv4_headers['user-agent'] = headers['user-agent']
|
769
|
-
end
|
770
|
-
|
771
|
-
headers = headers.merge(sigv4_headers) # merge so we do not modify given headers hash
|
772
|
-
|
773
|
-
config = Aws::Crt::Auth::SigningConfig.new(
|
774
|
-
algorithm: @signing_algorithm,
|
775
|
-
signature_type: :http_request_headers,
|
776
|
-
region: @region,
|
777
|
-
service: @service,
|
778
|
-
date: datetime,
|
779
|
-
signed_body_value: content_sha256,
|
780
|
-
signed_body_header_type: @apply_checksum_header ?
|
781
|
-
:sbht_content_sha256 : :sbht_none,
|
782
|
-
credentials: creds,
|
783
|
-
unsigned_headers: @unsigned_headers,
|
784
|
-
use_double_uri_encode: @uri_escape_path,
|
785
|
-
should_normalize_uri_path: @normalize_path,
|
786
|
-
omit_session_token: @omit_session_token
|
787
|
-
)
|
788
|
-
http_request = Aws::Crt::Http::Message.new(
|
789
|
-
http_method, url.to_s, headers
|
790
|
-
)
|
791
|
-
signable = Aws::Crt::Auth::Signable.new(http_request)
|
792
|
-
|
793
|
-
signing_result = Aws::Crt::Auth::Signer.sign_request(config, signable)
|
794
|
-
|
795
|
-
Signature.new(
|
796
|
-
headers: sigv4_headers.merge(
|
797
|
-
downcase_headers(signing_result[:headers])
|
798
|
-
),
|
799
|
-
string_to_sign: 'CRT_INTERNAL',
|
800
|
-
canonical_request: 'CRT_INTERNAL',
|
801
|
-
content_sha256: content_sha256,
|
802
|
-
extra: {config: config, signable: signable}
|
803
|
-
)
|
804
|
-
end
|
805
|
-
|
806
|
-
def crt_presign_url(options)
|
807
|
-
creds, expiration = crt_fetch_credentials
|
808
|
-
|
809
|
-
http_method = extract_http_method(options)
|
810
|
-
url = extract_url(options)
|
811
|
-
headers = downcase_headers(options[:headers])
|
812
|
-
headers['host'] ||= host(url)
|
813
|
-
|
814
|
-
datetime = headers.delete('x-amz-date')
|
815
|
-
datetime ||= (options[:time] || Time.now)
|
816
|
-
|
817
|
-
content_sha256 = headers.delete('x-amz-content-sha256')
|
818
|
-
content_sha256 ||= options[:body_digest]
|
819
|
-
content_sha256 ||= sha256_hexdigest(options[:body] || '')
|
820
|
-
|
821
|
-
config = Aws::Crt::Auth::SigningConfig.new(
|
822
|
-
algorithm: @signing_algorithm,
|
823
|
-
signature_type: :http_request_query_params,
|
824
|
-
region: @region,
|
825
|
-
service: @service,
|
826
|
-
date: datetime,
|
827
|
-
signed_body_value: content_sha256,
|
828
|
-
signed_body_header_type: @apply_checksum_header ?
|
829
|
-
:sbht_content_sha256 : :sbht_none,
|
830
|
-
credentials: creds,
|
831
|
-
unsigned_headers: @unsigned_headers,
|
832
|
-
use_double_uri_encode: @uri_escape_path,
|
833
|
-
should_normalize_uri_path: @normalize_path,
|
834
|
-
omit_session_token: @omit_session_token,
|
835
|
-
expiration_in_seconds: presigned_url_expiration(options, expiration)
|
836
|
-
)
|
837
|
-
http_request = Aws::Crt::Http::Message.new(
|
838
|
-
http_method, url.to_s, headers
|
839
|
-
)
|
840
|
-
signable = Aws::Crt::Auth::Signable.new(http_request)
|
841
|
-
|
842
|
-
signing_result = Aws::Crt::Auth::Signer.sign_request(config, signable, http_method, url.to_s)
|
843
|
-
url = URI.parse(signing_result[:path])
|
844
|
-
|
845
|
-
if options[:extra] && options[:extra].is_a?(Hash)
|
846
|
-
options[:extra][:config] = config
|
847
|
-
options[:extra][:signable] = signable
|
771
|
+
expiration_seconds = (expiration - datetime).to_i
|
772
|
+
# In the static stability case, credentials may expire in the past
|
773
|
+
# but still be valid. For those cases, use the user configured
|
774
|
+
# expires_in and ingore expiration.
|
775
|
+
if expiration_seconds <= 0
|
776
|
+
expires_in
|
777
|
+
else
|
778
|
+
[expires_in, expiration_seconds].min
|
848
779
|
end
|
849
|
-
url
|
850
780
|
end
|
851
781
|
|
852
782
|
class << self
|
853
783
|
|
784
|
+
# Kept for backwards compatability
|
785
|
+
# Always return false since we are not using crt signing functionality
|
854
786
|
def use_crt?
|
855
|
-
|
787
|
+
false
|
856
788
|
end
|
857
789
|
|
858
790
|
# @api private
|
@@ -869,6 +801,18 @@ module Aws
|
|
869
801
|
end
|
870
802
|
end
|
871
803
|
|
804
|
+
# @api private
|
805
|
+
def normalize_path(uri)
|
806
|
+
normalized_path = Pathname.new(uri.path).cleanpath.to_s
|
807
|
+
# Pathname is probably not correct to use. Empty paths will
|
808
|
+
# resolve to "." and should be disregarded
|
809
|
+
normalized_path = '' if normalized_path == '.'
|
810
|
+
# Ensure trailing slashes are correctly preserved
|
811
|
+
if uri.path.end_with?('/') && !normalized_path.end_with?('/')
|
812
|
+
normalized_path << '/'
|
813
|
+
end
|
814
|
+
uri.path = normalized_path
|
815
|
+
end
|
872
816
|
end
|
873
817
|
end
|
874
818
|
end
|
data/lib/aws-sigv4.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: aws-sigv4
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.12.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Amazon Web Services
|
8
|
-
autorequire:
|
9
8
|
bindir: bin
|
10
9
|
cert_chain: []
|
11
|
-
date:
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
12
11
|
dependencies:
|
13
12
|
- !ruby/object:Gem::Dependency
|
14
13
|
name: aws-eventstream
|
@@ -32,7 +31,6 @@ dependencies:
|
|
32
31
|
version: 1.0.2
|
33
32
|
description: Amazon Web Services Signature Version 4 signing library. Generates sigv4
|
34
33
|
signature for HTTP requests.
|
35
|
-
email:
|
36
34
|
executables: []
|
37
35
|
extensions: []
|
38
36
|
extra_rdoc_files: []
|
@@ -41,6 +39,7 @@ files:
|
|
41
39
|
- LICENSE.txt
|
42
40
|
- VERSION
|
43
41
|
- lib/aws-sigv4.rb
|
42
|
+
- lib/aws-sigv4/asymmetric_credentials.rb
|
44
43
|
- lib/aws-sigv4/credentials.rb
|
45
44
|
- lib/aws-sigv4/errors.rb
|
46
45
|
- lib/aws-sigv4/request.rb
|
@@ -52,7 +51,6 @@ licenses:
|
|
52
51
|
metadata:
|
53
52
|
source_code_uri: https://github.com/aws/aws-sdk-ruby/tree/version-3/gems/aws-sigv4
|
54
53
|
changelog_uri: https://github.com/aws/aws-sdk-ruby/tree/version-3/gems/aws-sigv4/CHANGELOG.md
|
55
|
-
post_install_message:
|
56
54
|
rdoc_options: []
|
57
55
|
require_paths:
|
58
56
|
- lib
|
@@ -60,15 +58,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
60
58
|
requirements:
|
61
59
|
- - ">="
|
62
60
|
- !ruby/object:Gem::Version
|
63
|
-
version: '2.
|
61
|
+
version: '2.7'
|
64
62
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
65
63
|
requirements:
|
66
64
|
- - ">="
|
67
65
|
- !ruby/object:Gem::Version
|
68
66
|
version: '0'
|
69
67
|
requirements: []
|
70
|
-
rubygems_version: 3.
|
71
|
-
signing_key:
|
68
|
+
rubygems_version: 3.6.7
|
72
69
|
specification_version: 4
|
73
70
|
summary: AWS Signature Version 4 library.
|
74
71
|
test_files: []
|