aws-sigv4 1.5.2 → 1.12.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 +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 +123 -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: 2f65828f524fd8437e342204ac5a7ae1deff92dfcfae2affbd636ceef0a1cb4d
|
4
|
+
data.tar.gz: 260bf03de93d779a544226ef512e958a3a6256bb189918be7a63c5c4bf58395f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 456725f9d6b39c36030e321854d1ef6361e504fb0af829fd95427cbf9762aba52be46f178f32db3ba332598b525ac9bbb287a19ade98b23892ff2d7613e3aca4
|
7
|
+
data.tar.gz: 80301bb28c264d5a73984e7b1ec3423b26daca0451996f47a21219d1d8dd615889c701678b9551cf3f724a6a6ab59c623fe71a89b62c9f54ff9dc38b849e058d
|
data/CHANGELOG.md
CHANGED
@@ -1,6 +1,56 @@
|
|
1
1
|
Unreleased Changes
|
2
2
|
------------------
|
3
3
|
|
4
|
+
1.12.0 (2025-06-02)
|
5
|
+
------------------
|
6
|
+
|
7
|
+
* Feature - AWS SDK for Ruby no longer supports Ruby runtime versions 2.5 and 2.6.
|
8
|
+
|
9
|
+
1.11.0 (2025-01-10)
|
10
|
+
------------------
|
11
|
+
|
12
|
+
* Feature - Add RBS signature files to support static type checking
|
13
|
+
|
14
|
+
1.10.1 (2024-10-21)
|
15
|
+
------------------
|
16
|
+
|
17
|
+
* Issue - Fix sigv4a signing issue with derive_asymmetric_key for certain credentials.
|
18
|
+
|
19
|
+
1.10.0 (2024-09-17)
|
20
|
+
------------------
|
21
|
+
|
22
|
+
* Feature - Remove CRT `sigv4a` signing capability.
|
23
|
+
|
24
|
+
1.9.1 (2024-07-29)
|
25
|
+
------------------
|
26
|
+
|
27
|
+
* Issue - Add missing require of `pathname` to `Signer`.
|
28
|
+
|
29
|
+
1.9.0 (2024-07-23)
|
30
|
+
------------------
|
31
|
+
|
32
|
+
* Feature - Support `sigv4a` signing algorithm without `aws-crt`.
|
33
|
+
|
34
|
+
1.8.0 (2023-11-28)
|
35
|
+
------------------
|
36
|
+
|
37
|
+
* Feature - Support `sigv4-s3express` signing algorithm.
|
38
|
+
|
39
|
+
1.7.0 (2023-11-22)
|
40
|
+
------------------
|
41
|
+
|
42
|
+
* Feature - AWS SDK for Ruby no longer supports Ruby runtime versions 2.3 and 2.4.
|
43
|
+
|
44
|
+
1.6.1 (2023-10-25)
|
45
|
+
------------------
|
46
|
+
|
47
|
+
* Issue - (Static Stability) use provided `expires_in` in presigned url when credentials are expired.
|
48
|
+
|
49
|
+
1.6.0 (2023-06-28)
|
50
|
+
------------------
|
51
|
+
|
52
|
+
* Feature - Select the minimum expiration time for presigned urls between the expiration time option and the credential expiration time.
|
53
|
+
|
4
54
|
1.5.2 (2022-09-30)
|
5
55
|
------------------
|
6
56
|
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
1.
|
1
|
+
1.12.0
|
@@ -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
@@ -6,6 +6,7 @@ require 'time'
|
|
6
6
|
require 'uri'
|
7
7
|
require 'set'
|
8
8
|
require 'cgi'
|
9
|
+
require 'pathname'
|
9
10
|
require 'aws-eventstream'
|
10
11
|
|
11
12
|
module Aws
|
@@ -73,25 +74,18 @@ module Aws
|
|
73
74
|
# and `#session_token`.
|
74
75
|
#
|
75
76
|
class Signer
|
76
|
-
|
77
|
-
@@use_crt =
|
78
|
-
begin
|
79
|
-
require 'aws-crt'
|
80
|
-
true
|
81
|
-
rescue LoadError
|
82
|
-
false
|
83
|
-
end
|
84
|
-
|
85
77
|
# @overload initialize(service:, region:, access_key_id:, secret_access_key:, session_token:nil, **options)
|
86
78
|
# @param [String] :service The service signing name, e.g. 's3'.
|
87
|
-
# @param [String] :region The region name, e.g. 'us-east-1'.
|
79
|
+
# @param [String] :region The region name, e.g. 'us-east-1'. When signing
|
80
|
+
# with sigv4a, this should be a comma separated list of regions.
|
88
81
|
# @param [String] :access_key_id
|
89
82
|
# @param [String] :secret_access_key
|
90
83
|
# @param [String] :session_token (nil)
|
91
84
|
#
|
92
85
|
# @overload initialize(service:, region:, credentials:, **options)
|
93
86
|
# @param [String] :service The service signing name, e.g. 's3'.
|
94
|
-
# @param [String] :region The region name, e.g. 'us-east-1'.
|
87
|
+
# @param [String] :region The region name, e.g. 'us-east-1'. When signing
|
88
|
+
# with sigv4a, this should be a comma separated list of regions.
|
95
89
|
# @param [Credentials] :credentials Any object that responds to the following
|
96
90
|
# methods:
|
97
91
|
#
|
@@ -102,7 +96,8 @@ module Aws
|
|
102
96
|
#
|
103
97
|
# @overload initialize(service:, region:, credentials_provider:, **options)
|
104
98
|
# @param [String] :service The service signing name, e.g. 's3'.
|
105
|
-
# @param [String] :region The region name, e.g. 'us-east-1'.
|
99
|
+
# @param [String] :region The region name, e.g. 'us-east-1'. When signing
|
100
|
+
# with sigv4a, this should be a comma separated list of regions.
|
106
101
|
# @param [#credentials] :credentials_provider An object that responds
|
107
102
|
# to `#credentials`, returning an object that responds to the following
|
108
103
|
# methods:
|
@@ -127,8 +122,7 @@ module Aws
|
|
127
122
|
# every other AWS service as of late 2016.
|
128
123
|
#
|
129
124
|
# @option options [Symbol] :signing_algorithm (:sigv4) The
|
130
|
-
# algorithm to use for signing.
|
131
|
-
# `aws-crt` is available.
|
125
|
+
# algorithm to use for signing.
|
132
126
|
#
|
133
127
|
# @option options [Boolean] :omit_session_token (false)
|
134
128
|
# (Supported only when `aws-crt` is available) If `true`,
|
@@ -136,8 +130,8 @@ module Aws
|
|
136
130
|
# but is treated as "unsigned" and does not contribute
|
137
131
|
# to the authorization signature.
|
138
132
|
#
|
139
|
-
# @option options [Boolean] :normalize_path (true)
|
140
|
-
#
|
133
|
+
# @option options [Boolean] :normalize_path (true) When `true`, the
|
134
|
+
# uri paths will be normalized when building the canonical request.
|
141
135
|
def initialize(options = {})
|
142
136
|
@service = extract_service(options)
|
143
137
|
@region = extract_region(options)
|
@@ -151,12 +145,6 @@ module Aws
|
|
151
145
|
@signing_algorithm = options.fetch(:signing_algorithm, :sigv4)
|
152
146
|
@normalize_path = options.fetch(:normalize_path, true)
|
153
147
|
@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
148
|
end
|
161
149
|
|
162
150
|
# @return [String]
|
@@ -217,7 +205,7 @@ module Aws
|
|
217
205
|
# @option request [required, String] :http_method One of
|
218
206
|
# 'GET', 'HEAD', 'PUT', 'POST', 'PATCH', or 'DELETE'
|
219
207
|
#
|
220
|
-
# @option request [required, String, URI::
|
208
|
+
# @option request [required, String, URI::HTTP, URI::HTTPS] :url
|
221
209
|
# The request URI. Must be a valid HTTP or HTTPS URI.
|
222
210
|
#
|
223
211
|
# @option request [optional, Hash] :headers ({}) A hash of headers
|
@@ -232,13 +220,11 @@ module Aws
|
|
232
220
|
# a `#headers` method. The headers must be applied to your request.
|
233
221
|
#
|
234
222
|
def sign_request(request)
|
235
|
-
|
236
|
-
return crt_sign_request(request) if Signer.use_crt?
|
237
|
-
|
238
|
-
creds = fetch_credentials
|
223
|
+
creds, _ = fetch_credentials
|
239
224
|
|
240
225
|
http_method = extract_http_method(request)
|
241
226
|
url = extract_url(request)
|
227
|
+
Signer.normalize_path(url) if @normalize_path
|
242
228
|
headers = downcase_headers(request[:headers])
|
243
229
|
|
244
230
|
datetime = headers['x-amz-date']
|
@@ -251,29 +237,55 @@ module Aws
|
|
251
237
|
sigv4_headers = {}
|
252
238
|
sigv4_headers['host'] = headers['host'] || host(url)
|
253
239
|
sigv4_headers['x-amz-date'] = datetime
|
254
|
-
|
240
|
+
if creds.session_token && !@omit_session_token
|
241
|
+
if @signing_algorithm == 'sigv4-s3express'.to_sym
|
242
|
+
sigv4_headers['x-amz-s3session-token'] = creds.session_token
|
243
|
+
else
|
244
|
+
sigv4_headers['x-amz-security-token'] = creds.session_token
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
255
248
|
sigv4_headers['x-amz-content-sha256'] ||= content_sha256 if @apply_checksum_header
|
256
249
|
|
250
|
+
if @signing_algorithm == :sigv4a && @region && !@region.empty?
|
251
|
+
sigv4_headers['x-amz-region-set'] = @region
|
252
|
+
end
|
257
253
|
headers = headers.merge(sigv4_headers) # merge so we do not modify given headers hash
|
258
254
|
|
255
|
+
algorithm = sts_algorithm
|
256
|
+
|
259
257
|
# compute signature parts
|
260
258
|
creq = canonical_request(http_method, url, headers, content_sha256)
|
261
|
-
sts = string_to_sign(datetime, creq)
|
262
|
-
|
259
|
+
sts = string_to_sign(datetime, creq, algorithm)
|
260
|
+
|
261
|
+
sig =
|
262
|
+
if @signing_algorithm == :sigv4a
|
263
|
+
asymmetric_signature(creds, sts)
|
264
|
+
else
|
265
|
+
signature(creds.secret_access_key, date, sts)
|
266
|
+
end
|
267
|
+
|
268
|
+
algorithm = sts_algorithm
|
263
269
|
|
264
270
|
# apply signature
|
265
271
|
sigv4_headers['authorization'] = [
|
266
|
-
"
|
272
|
+
"#{algorithm} Credential=#{credential(creds, date)}",
|
267
273
|
"SignedHeaders=#{signed_headers(headers)}",
|
268
274
|
"Signature=#{sig}",
|
269
275
|
].join(', ')
|
270
276
|
|
277
|
+
# skip signing the session token, but include it in the headers
|
278
|
+
if creds.session_token && @omit_session_token
|
279
|
+
sigv4_headers['x-amz-security-token'] = creds.session_token
|
280
|
+
end
|
281
|
+
|
271
282
|
# Returning the signature components.
|
272
283
|
Signature.new(
|
273
284
|
headers: sigv4_headers,
|
274
285
|
string_to_sign: sts,
|
275
286
|
canonical_request: creq,
|
276
|
-
content_sha256: content_sha256
|
287
|
+
content_sha256: content_sha256,
|
288
|
+
signature: sig
|
277
289
|
)
|
278
290
|
end
|
279
291
|
|
@@ -313,8 +325,7 @@ module Aws
|
|
313
325
|
# signature value (a binary string) used at ':chunk-signature' needs to converted to
|
314
326
|
# hex-encoded string using #unpack
|
315
327
|
def sign_event(prior_signature, payload, encoder)
|
316
|
-
|
317
|
-
creds = fetch_credentials
|
328
|
+
creds, _ = fetch_credentials
|
318
329
|
time = Time.now
|
319
330
|
headers = {}
|
320
331
|
|
@@ -372,7 +383,7 @@ module Aws
|
|
372
383
|
# @option options [required, String] :http_method The HTTP request method,
|
373
384
|
# e.g. 'GET', 'HEAD', 'PUT', 'POST', 'PATCH', or 'DELETE'.
|
374
385
|
#
|
375
|
-
# @option options [required, String,
|
386
|
+
# @option options [required, String, URI::HTTP, URI::HTTPS] :url
|
376
387
|
# The URI to sign.
|
377
388
|
#
|
378
389
|
# @option options [Hash] :headers ({}) Headers that should
|
@@ -400,13 +411,11 @@ module Aws
|
|
400
411
|
# @return [HTTPS::URI, HTTP::URI]
|
401
412
|
#
|
402
413
|
def presign_url(options)
|
403
|
-
|
404
|
-
return crt_presign_url(options) if Signer.use_crt?
|
405
|
-
|
406
|
-
creds = fetch_credentials
|
414
|
+
creds, expiration = fetch_credentials
|
407
415
|
|
408
416
|
http_method = extract_http_method(options)
|
409
417
|
url = extract_url(options)
|
418
|
+
Signer.normalize_path(url) if @normalize_path
|
410
419
|
|
411
420
|
headers = downcase_headers(options[:headers])
|
412
421
|
headers['host'] ||= host(url)
|
@@ -419,14 +428,26 @@ module Aws
|
|
419
428
|
content_sha256 ||= options[:body_digest]
|
420
429
|
content_sha256 ||= sha256_hexdigest(options[:body] || '')
|
421
430
|
|
431
|
+
algorithm = sts_algorithm
|
432
|
+
|
422
433
|
params = {}
|
423
|
-
params['X-Amz-Algorithm'] =
|
434
|
+
params['X-Amz-Algorithm'] = algorithm
|
424
435
|
params['X-Amz-Credential'] = credential(creds, date)
|
425
436
|
params['X-Amz-Date'] = datetime
|
426
|
-
params['X-Amz-Expires'] =
|
427
|
-
|
437
|
+
params['X-Amz-Expires'] = presigned_url_expiration(options, expiration, Time.strptime(datetime, "%Y%m%dT%H%M%S%Z")).to_s
|
438
|
+
if creds.session_token
|
439
|
+
if @signing_algorithm == 'sigv4-s3express'.to_sym
|
440
|
+
params['X-Amz-S3session-Token'] = creds.session_token
|
441
|
+
else
|
442
|
+
params['X-Amz-Security-Token'] = creds.session_token
|
443
|
+
end
|
444
|
+
end
|
428
445
|
params['X-Amz-SignedHeaders'] = signed_headers(headers)
|
429
446
|
|
447
|
+
if @signing_algorithm == :sigv4a && @region
|
448
|
+
params['X-Amz-Region-Set'] = @region
|
449
|
+
end
|
450
|
+
|
430
451
|
params = params.map do |key, value|
|
431
452
|
"#{uri_escape(key)}=#{uri_escape(value)}"
|
432
453
|
end.join('&')
|
@@ -438,13 +459,23 @@ module Aws
|
|
438
459
|
end
|
439
460
|
|
440
461
|
creq = canonical_request(http_method, url, headers, content_sha256)
|
441
|
-
sts = string_to_sign(datetime, creq)
|
442
|
-
|
462
|
+
sts = string_to_sign(datetime, creq, algorithm)
|
463
|
+
signature =
|
464
|
+
if @signing_algorithm == :sigv4a
|
465
|
+
asymmetric_signature(creds, sts)
|
466
|
+
else
|
467
|
+
signature(creds.secret_access_key, date, sts)
|
468
|
+
end
|
469
|
+
url.query += '&X-Amz-Signature=' + signature
|
443
470
|
url
|
444
471
|
end
|
445
472
|
|
446
473
|
private
|
447
474
|
|
475
|
+
def sts_algorithm
|
476
|
+
@signing_algorithm == :sigv4a ? 'AWS4-ECDSA-P256-SHA256' : 'AWS4-HMAC-SHA256'
|
477
|
+
end
|
478
|
+
|
448
479
|
def canonical_request(http_method, url, headers, content_sha256)
|
449
480
|
[
|
450
481
|
http_method,
|
@@ -456,9 +487,9 @@ module Aws
|
|
456
487
|
].join("\n")
|
457
488
|
end
|
458
489
|
|
459
|
-
def string_to_sign(datetime, canonical_request)
|
490
|
+
def string_to_sign(datetime, canonical_request, algorithm)
|
460
491
|
[
|
461
|
-
|
492
|
+
algorithm,
|
462
493
|
datetime,
|
463
494
|
credential_scope(datetime[0,8]),
|
464
495
|
sha256_hexdigest(canonical_request),
|
@@ -491,10 +522,10 @@ module Aws
|
|
491
522
|
def credential_scope(date)
|
492
523
|
[
|
493
524
|
date,
|
494
|
-
@region,
|
525
|
+
(@region unless @signing_algorithm == :sigv4a),
|
495
526
|
@service,
|
496
|
-
'aws4_request'
|
497
|
-
].join('/')
|
527
|
+
'aws4_request'
|
528
|
+
].compact.join('/')
|
498
529
|
end
|
499
530
|
|
500
531
|
def credential(credentials, date)
|
@@ -509,6 +540,16 @@ module Aws
|
|
509
540
|
hexhmac(k_credentials, string_to_sign)
|
510
541
|
end
|
511
542
|
|
543
|
+
def asymmetric_signature(creds, string_to_sign)
|
544
|
+
ec, _ = Aws::Sigv4::AsymmetricCredentials.derive_asymmetric_key(
|
545
|
+
creds.access_key_id, creds.secret_access_key
|
546
|
+
)
|
547
|
+
sts_digest = OpenSSL::Digest::SHA256.digest(string_to_sign)
|
548
|
+
s = ec.dsa_sign_asn1(sts_digest)
|
549
|
+
|
550
|
+
Digest.hexencode(s)
|
551
|
+
end
|
552
|
+
|
512
553
|
# Comparing to original signature v4 algorithm,
|
513
554
|
# returned signature is a binary string instread of
|
514
555
|
# hex-encoded string. (Since ':chunk-signature' requires
|
@@ -526,7 +567,6 @@ module Aws
|
|
526
567
|
hmac(k_credentials, string_to_sign)
|
527
568
|
end
|
528
569
|
|
529
|
-
|
530
570
|
def path(url)
|
531
571
|
path = url.path
|
532
572
|
path = '/' if path == ''
|
@@ -682,8 +722,8 @@ module Aws
|
|
682
722
|
|
683
723
|
def extract_expires_in(options)
|
684
724
|
case options[:expires_in]
|
685
|
-
when nil then 900
|
686
|
-
when Integer then options[:expires_in]
|
725
|
+
when nil then 900
|
726
|
+
when Integer then options[:expires_in]
|
687
727
|
else
|
688
728
|
msg = "expected :expires_in to be a number of seconds"
|
689
729
|
raise ArgumentError, msg
|
@@ -698,11 +738,14 @@ module Aws
|
|
698
738
|
self.class.uri_escape_path(string)
|
699
739
|
end
|
700
740
|
|
701
|
-
|
702
741
|
def fetch_credentials
|
703
742
|
credentials = @credentials_provider.credentials
|
704
743
|
if credentials_set?(credentials)
|
705
|
-
|
744
|
+
expiration = nil
|
745
|
+
if @credentials_provider.respond_to?(:expiration)
|
746
|
+
expiration = @credentials_provider.expiration
|
747
|
+
end
|
748
|
+
[credentials, expiration]
|
706
749
|
else
|
707
750
|
raise Errors::MissingCredentialsError,
|
708
751
|
'unable to sign request without credentials set'
|
@@ -720,128 +763,27 @@ module Aws
|
|
720
763
|
!credentials.secret_access_key.empty?
|
721
764
|
end
|
722
765
|
|
723
|
-
|
766
|
+
def presigned_url_expiration(options, expiration, datetime)
|
767
|
+
expires_in = extract_expires_in(options)
|
768
|
+
return expires_in unless expiration
|
724
769
|
|
725
|
-
|
726
|
-
|
727
|
-
|
728
|
-
|
729
|
-
|
730
|
-
|
731
|
-
|
732
|
-
|
733
|
-
)
|
734
|
-
end
|
735
|
-
|
736
|
-
def crt_sign_request(request)
|
737
|
-
creds = crt_fetch_credentials
|
738
|
-
http_method = extract_http_method(request)
|
739
|
-
url = extract_url(request)
|
740
|
-
headers = downcase_headers(request[:headers])
|
741
|
-
|
742
|
-
datetime =
|
743
|
-
if headers.include? 'x-amz-date'
|
744
|
-
Time.parse(headers.delete('x-amz-date'))
|
745
|
-
end
|
746
|
-
|
747
|
-
content_sha256 = headers.delete('x-amz-content-sha256')
|
748
|
-
content_sha256 ||= sha256_hexdigest(request[:body] || '')
|
749
|
-
|
750
|
-
sigv4_headers = {}
|
751
|
-
sigv4_headers['host'] = headers['host'] || host(url)
|
752
|
-
|
753
|
-
# Modify the user-agent to add usage of crt-signer
|
754
|
-
# This should be temporary during developer preview only
|
755
|
-
if headers.include? 'user-agent'
|
756
|
-
headers['user-agent'] = "#{headers['user-agent']} crt-signer/#{@signing_algorithm}/#{Aws::Sigv4::VERSION}"
|
757
|
-
sigv4_headers['user-agent'] = headers['user-agent']
|
758
|
-
end
|
759
|
-
|
760
|
-
headers = headers.merge(sigv4_headers) # merge so we do not modify given headers hash
|
761
|
-
|
762
|
-
config = Aws::Crt::Auth::SigningConfig.new(
|
763
|
-
algorithm: @signing_algorithm,
|
764
|
-
signature_type: :http_request_headers,
|
765
|
-
region: @region,
|
766
|
-
service: @service,
|
767
|
-
date: datetime,
|
768
|
-
signed_body_value: content_sha256,
|
769
|
-
signed_body_header_type: @apply_checksum_header ?
|
770
|
-
:sbht_content_sha256 : :sbht_none,
|
771
|
-
credentials: creds,
|
772
|
-
unsigned_headers: @unsigned_headers,
|
773
|
-
use_double_uri_encode: @uri_escape_path,
|
774
|
-
should_normalize_uri_path: @normalize_path,
|
775
|
-
omit_session_token: @omit_session_token
|
776
|
-
)
|
777
|
-
http_request = Aws::Crt::Http::Message.new(
|
778
|
-
http_method, url.to_s, headers
|
779
|
-
)
|
780
|
-
signable = Aws::Crt::Auth::Signable.new(http_request)
|
781
|
-
|
782
|
-
signing_result = Aws::Crt::Auth::Signer.sign_request(config, signable)
|
783
|
-
|
784
|
-
Signature.new(
|
785
|
-
headers: sigv4_headers.merge(
|
786
|
-
downcase_headers(signing_result[:headers])
|
787
|
-
),
|
788
|
-
string_to_sign: 'CRT_INTERNAL',
|
789
|
-
canonical_request: 'CRT_INTERNAL',
|
790
|
-
content_sha256: content_sha256,
|
791
|
-
extra: {config: config, signable: signable}
|
792
|
-
)
|
793
|
-
end
|
794
|
-
|
795
|
-
def crt_presign_url(options)
|
796
|
-
creds = crt_fetch_credentials
|
797
|
-
|
798
|
-
http_method = extract_http_method(options)
|
799
|
-
url = extract_url(options)
|
800
|
-
headers = downcase_headers(options[:headers])
|
801
|
-
headers['host'] ||= host(url)
|
802
|
-
|
803
|
-
datetime = headers.delete('x-amz-date')
|
804
|
-
datetime ||= (options[:time] || Time.now)
|
805
|
-
|
806
|
-
content_sha256 = headers.delete('x-amz-content-sha256')
|
807
|
-
content_sha256 ||= options[:body_digest]
|
808
|
-
content_sha256 ||= sha256_hexdigest(options[:body] || '')
|
809
|
-
|
810
|
-
config = Aws::Crt::Auth::SigningConfig.new(
|
811
|
-
algorithm: @signing_algorithm,
|
812
|
-
signature_type: :http_request_query_params,
|
813
|
-
region: @region,
|
814
|
-
service: @service,
|
815
|
-
date: datetime,
|
816
|
-
signed_body_value: content_sha256,
|
817
|
-
signed_body_header_type: @apply_checksum_header ?
|
818
|
-
:sbht_content_sha256 : :sbht_none,
|
819
|
-
credentials: creds,
|
820
|
-
unsigned_headers: @unsigned_headers,
|
821
|
-
use_double_uri_encode: @uri_escape_path,
|
822
|
-
should_normalize_uri_path: @normalize_path,
|
823
|
-
omit_session_token: @omit_session_token,
|
824
|
-
expiration_in_seconds: options.fetch(:expires_in, 900)
|
825
|
-
)
|
826
|
-
http_request = Aws::Crt::Http::Message.new(
|
827
|
-
http_method, url.to_s, headers
|
828
|
-
)
|
829
|
-
signable = Aws::Crt::Auth::Signable.new(http_request)
|
830
|
-
|
831
|
-
signing_result = Aws::Crt::Auth::Signer.sign_request(config, signable, http_method, url.to_s)
|
832
|
-
url = URI.parse(signing_result[:path])
|
833
|
-
|
834
|
-
if options[:extra] && options[:extra].is_a?(Hash)
|
835
|
-
options[:extra][:config] = config
|
836
|
-
options[:extra][:signable] = signable
|
770
|
+
expiration_seconds = (expiration - datetime).to_i
|
771
|
+
# In the static stability case, credentials may expire in the past
|
772
|
+
# but still be valid. For those cases, use the user configured
|
773
|
+
# expires_in and ingore expiration.
|
774
|
+
if expiration_seconds <= 0
|
775
|
+
expires_in
|
776
|
+
else
|
777
|
+
[expires_in, expiration_seconds].min
|
837
778
|
end
|
838
|
-
url
|
839
779
|
end
|
840
780
|
|
841
781
|
class << self
|
842
782
|
|
783
|
+
# Kept for backwards compatability
|
784
|
+
# Always return false since we are not using crt signing functionality
|
843
785
|
def use_crt?
|
844
|
-
|
786
|
+
false
|
845
787
|
end
|
846
788
|
|
847
789
|
# @api private
|
@@ -858,6 +800,18 @@ module Aws
|
|
858
800
|
end
|
859
801
|
end
|
860
802
|
|
803
|
+
# @api private
|
804
|
+
def normalize_path(uri)
|
805
|
+
normalized_path = Pathname.new(uri.path).cleanpath.to_s
|
806
|
+
# Pathname is probably not correct to use. Empty paths will
|
807
|
+
# resolve to "." and should be disregarded
|
808
|
+
normalized_path = '' if normalized_path == '.'
|
809
|
+
# Ensure trailing slashes are correctly preserved
|
810
|
+
if uri.path.end_with?('/') && !normalized_path.end_with?('/')
|
811
|
+
normalized_path << '/'
|
812
|
+
end
|
813
|
+
uri.path = normalized_path
|
814
|
+
end
|
861
815
|
end
|
862
816
|
end
|
863
817
|
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.0
|
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: []
|