aws-sigv4 1.8.0 → 1.9.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 +5 -0
- data/VERSION +1 -1
- data/lib/aws-sigv4/asymmetric_credentials.rb +91 -0
- data/lib/aws-sigv4/signature.rb +3 -0
- data/lib/aws-sigv4/signer.rb +81 -26
- data/lib/aws-sigv4.rb +1 -0
- metadata +8 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 25c3ddf60803af303d37af469c7250ceba22d6a23e62bbfb7a9a82ae3d01b8e8
|
4
|
+
data.tar.gz: 36537ef12949c9d0a632060485c3dcf340176a8241c3e65828e47991581e7c89
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: aa33926ae5a1804fee36cce9b7cadead40d9d5806154e62840be377c663d94dbac07ea537601f4fa47c1d4861dccb3bdf7801b2b1edf256a0a452a73fdf2c9de
|
7
|
+
data.tar.gz: 8e1e705a6dfef2edd5af640de60f01321f1a811f41f407e906f08881d83d197ba9011c4ed3d2a218f6f17f94fcd602e0a6759abcf7c5e5e27f5d66465c3f3f3c
|
data/CHANGELOG.md
CHANGED
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
1.
|
1
|
+
1.9.0
|
@@ -0,0 +1,91 @@
|
|
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
|
+
# @param [String] :access_key_id
|
15
|
+
# @param [String] :secret_access_key
|
16
|
+
# @return [OpenSSL::PKey::EC, Hash]
|
17
|
+
def self.derive_asymmetric_key(access_key_id, secret_access_key)
|
18
|
+
check_openssl_support!
|
19
|
+
label = 'AWS4-ECDSA-P256-SHA256'
|
20
|
+
bit_len = 256
|
21
|
+
counter = 0x1
|
22
|
+
input_key = "AWS4A#{secret_access_key}"
|
23
|
+
d = 0 # d will end up being the private key
|
24
|
+
while true do
|
25
|
+
|
26
|
+
kdf_context = access_key_id.unpack('C*') + [counter].pack('C').unpack('C') #1 byte for counter
|
27
|
+
input = label.unpack('C*') + [0x00] + kdf_context + [bit_len].pack('L>').unpack('CCCC') # 4 bytes (change endianess)
|
28
|
+
k0 = OpenSSL::HMAC.digest("SHA256", input_key, ([0, 0, 0, 0x01] + input).pack('C*'))
|
29
|
+
c = be_bytes_to_num( k0.unpack('C*') )
|
30
|
+
if c <= N_MINUS_2
|
31
|
+
d = c + 1
|
32
|
+
break
|
33
|
+
elsif counter > 0xFF
|
34
|
+
raise 'Counter exceeded 1 byte - unable to get asym creds'
|
35
|
+
else
|
36
|
+
counter += 1
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# compute the public key
|
41
|
+
group = OpenSSL::PKey::EC::Group.new('prime256v1')
|
42
|
+
public_key = group.generator.mul(d)
|
43
|
+
|
44
|
+
ec = generate_ec(public_key, d)
|
45
|
+
|
46
|
+
# pk_x and pk_y are not needed for signature, but useful in verification/testing
|
47
|
+
pk_b = public_key.to_octet_string(:uncompressed).unpack('C*') # 0x04 byte followed by 2 32-byte integers
|
48
|
+
pk_x = be_bytes_to_num(pk_b[1,32])
|
49
|
+
pk_y = be_bytes_to_num(pk_b[33,32])
|
50
|
+
[ec, {ec: ec, public_key: public_key, pk_x: pk_x, pk_y: pk_y, d: d}]
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
# @return [Number] The value of the bytes interpreted as a big-endian
|
56
|
+
# unsigned integer.
|
57
|
+
def self.be_bytes_to_num(bytes)
|
58
|
+
x = 0
|
59
|
+
bytes.each { |b| x = (x*256) + b }
|
60
|
+
x
|
61
|
+
end
|
62
|
+
|
63
|
+
# Prior to openssl3 we could directly set public and private key on EC
|
64
|
+
# However, openssl3 deprecated those methods and we must now construct
|
65
|
+
# a der with the keys and load the EC from it.
|
66
|
+
def self.generate_ec(public_key, d)
|
67
|
+
# format reversed from: OpenSSL::ASN1.decode_all(OpenSSL::PKey::EC.new.to_der)
|
68
|
+
asn1 = OpenSSL::ASN1::Sequence([
|
69
|
+
OpenSSL::ASN1::Integer(OpenSSL::BN.new(1)),
|
70
|
+
OpenSSL::ASN1::OctetString([d.to_s(16)].pack('H*')),
|
71
|
+
OpenSSL::ASN1::ASN1Data.new([OpenSSL::ASN1::ObjectId("prime256v1")], 0, :CONTEXT_SPECIFIC),
|
72
|
+
OpenSSL::ASN1::ASN1Data.new(
|
73
|
+
[OpenSSL::ASN1::BitString(public_key.to_octet_string(:uncompressed))],
|
74
|
+
1, :CONTEXT_SPECIFIC
|
75
|
+
)
|
76
|
+
])
|
77
|
+
OpenSSL::PKey::EC.new(asn1.to_der)
|
78
|
+
end
|
79
|
+
|
80
|
+
def self.check_openssl_support!
|
81
|
+
return true unless defined?(JRUBY_VERSION)
|
82
|
+
|
83
|
+
# See: https://github.com/jruby/jruby-openssl/issues/306
|
84
|
+
# JRuby-openssl < 0.15 does not support OpenSSL::PKey::EC::Point#mul
|
85
|
+
return true if OpenSSL::PKey::EC::Point.instance_methods.include?(:mul)
|
86
|
+
|
87
|
+
raise 'Sigv4a Asymmetric Credential derivation requires jruby-openssl >= 0.15'
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
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
@@ -84,14 +84,16 @@ module Aws
|
|
84
84
|
|
85
85
|
# @overload initialize(service:, region:, access_key_id:, secret_access_key:, session_token:nil, **options)
|
86
86
|
# @param [String] :service The service signing name, e.g. 's3'.
|
87
|
-
# @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.
|
88
89
|
# @param [String] :access_key_id
|
89
90
|
# @param [String] :secret_access_key
|
90
91
|
# @param [String] :session_token (nil)
|
91
92
|
#
|
92
93
|
# @overload initialize(service:, region:, credentials:, **options)
|
93
94
|
# @param [String] :service The service signing name, e.g. 's3'.
|
94
|
-
# @param [String] :region The region name, e.g. 'us-east-1'.
|
95
|
+
# @param [String] :region The region name, e.g. 'us-east-1'. When signing
|
96
|
+
# with sigv4a, this should be a comma separated list of regions.
|
95
97
|
# @param [Credentials] :credentials Any object that responds to the following
|
96
98
|
# methods:
|
97
99
|
#
|
@@ -102,7 +104,8 @@ module Aws
|
|
102
104
|
#
|
103
105
|
# @overload initialize(service:, region:, credentials_provider:, **options)
|
104
106
|
# @param [String] :service The service signing name, e.g. 's3'.
|
105
|
-
# @param [String] :region The region name, e.g. 'us-east-1'.
|
107
|
+
# @param [String] :region The region name, e.g. 'us-east-1'. When signing
|
108
|
+
# with sigv4a, this should be a comma separated list of regions.
|
106
109
|
# @param [#credentials] :credentials_provider An object that responds
|
107
110
|
# to `#credentials`, returning an object that responds to the following
|
108
111
|
# methods:
|
@@ -127,8 +130,7 @@ module Aws
|
|
127
130
|
# every other AWS service as of late 2016.
|
128
131
|
#
|
129
132
|
# @option options [Symbol] :signing_algorithm (:sigv4) The
|
130
|
-
# algorithm to use for signing.
|
131
|
-
# `aws-crt` is available.
|
133
|
+
# algorithm to use for signing.
|
132
134
|
#
|
133
135
|
# @option options [Boolean] :omit_session_token (false)
|
134
136
|
# (Supported only when `aws-crt` is available) If `true`,
|
@@ -136,8 +138,8 @@ module Aws
|
|
136
138
|
# but is treated as "unsigned" and does not contribute
|
137
139
|
# to the authorization signature.
|
138
140
|
#
|
139
|
-
# @option options [Boolean] :normalize_path (true)
|
140
|
-
#
|
141
|
+
# @option options [Boolean] :normalize_path (true) When `true`, the
|
142
|
+
# uri paths will be normalized when building the canonical request.
|
141
143
|
def initialize(options = {})
|
142
144
|
@service = extract_service(options)
|
143
145
|
@region = extract_region(options)
|
@@ -152,12 +154,6 @@ module Aws
|
|
152
154
|
@normalize_path = options.fetch(:normalize_path, true)
|
153
155
|
@omit_session_token = options.fetch(:omit_session_token, false)
|
154
156
|
|
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
|
-
|
161
157
|
if @signing_algorithm == 'sigv4-s3express'.to_sym &&
|
162
158
|
Signer.use_crt? && Aws::Crt::GEM_VERSION <= '0.1.9'
|
163
159
|
raise ArgumentError,
|
@@ -246,6 +242,7 @@ module Aws
|
|
246
242
|
|
247
243
|
http_method = extract_http_method(request)
|
248
244
|
url = extract_url(request)
|
245
|
+
Signer.normalize_path(url) if @normalize_path
|
249
246
|
headers = downcase_headers(request[:headers])
|
250
247
|
|
251
248
|
datetime = headers['x-amz-date']
|
@@ -258,7 +255,7 @@ module Aws
|
|
258
255
|
sigv4_headers = {}
|
259
256
|
sigv4_headers['host'] = headers['host'] || host(url)
|
260
257
|
sigv4_headers['x-amz-date'] = datetime
|
261
|
-
if creds.session_token
|
258
|
+
if creds.session_token && !@omit_session_token
|
262
259
|
if @signing_algorithm == 'sigv4-s3express'.to_sym
|
263
260
|
sigv4_headers['x-amz-s3session-token'] = creds.session_token
|
264
261
|
else
|
@@ -268,26 +265,45 @@ module Aws
|
|
268
265
|
|
269
266
|
sigv4_headers['x-amz-content-sha256'] ||= content_sha256 if @apply_checksum_header
|
270
267
|
|
268
|
+
if @signing_algorithm == :sigv4a && @region && !@region.empty?
|
269
|
+
sigv4_headers['x-amz-region-set'] = @region
|
270
|
+
end
|
271
271
|
headers = headers.merge(sigv4_headers) # merge so we do not modify given headers hash
|
272
272
|
|
273
|
+
algorithm = sts_algorithm
|
274
|
+
|
273
275
|
# compute signature parts
|
274
276
|
creq = canonical_request(http_method, url, headers, content_sha256)
|
275
|
-
sts = string_to_sign(datetime, creq)
|
276
|
-
|
277
|
+
sts = string_to_sign(datetime, creq, algorithm)
|
278
|
+
|
279
|
+
sig =
|
280
|
+
if @signing_algorithm == :sigv4a
|
281
|
+
asymmetric_signature(creds, sts)
|
282
|
+
else
|
283
|
+
signature(creds.secret_access_key, date, sts)
|
284
|
+
end
|
285
|
+
|
286
|
+
algorithm = sts_algorithm
|
277
287
|
|
278
288
|
# apply signature
|
279
289
|
sigv4_headers['authorization'] = [
|
280
|
-
"
|
290
|
+
"#{algorithm} Credential=#{credential(creds, date)}",
|
281
291
|
"SignedHeaders=#{signed_headers(headers)}",
|
282
292
|
"Signature=#{sig}",
|
283
293
|
].join(', ')
|
284
294
|
|
295
|
+
# skip signing the session token, but include it in the headers
|
296
|
+
if creds.session_token && @omit_session_token
|
297
|
+
sigv4_headers['x-amz-security-token'] = creds.session_token
|
298
|
+
end
|
299
|
+
|
285
300
|
# Returning the signature components.
|
286
301
|
Signature.new(
|
287
302
|
headers: sigv4_headers,
|
288
303
|
string_to_sign: sts,
|
289
304
|
canonical_request: creq,
|
290
|
-
content_sha256: content_sha256
|
305
|
+
content_sha256: content_sha256,
|
306
|
+
signature: sig
|
291
307
|
)
|
292
308
|
end
|
293
309
|
|
@@ -421,6 +437,7 @@ module Aws
|
|
421
437
|
|
422
438
|
http_method = extract_http_method(options)
|
423
439
|
url = extract_url(options)
|
440
|
+
Signer.normalize_path(url) if @normalize_path
|
424
441
|
|
425
442
|
headers = downcase_headers(options[:headers])
|
426
443
|
headers['host'] ||= host(url)
|
@@ -433,8 +450,10 @@ module Aws
|
|
433
450
|
content_sha256 ||= options[:body_digest]
|
434
451
|
content_sha256 ||= sha256_hexdigest(options[:body] || '')
|
435
452
|
|
453
|
+
algorithm = sts_algorithm
|
454
|
+
|
436
455
|
params = {}
|
437
|
-
params['X-Amz-Algorithm'] =
|
456
|
+
params['X-Amz-Algorithm'] = algorithm
|
438
457
|
params['X-Amz-Credential'] = credential(creds, date)
|
439
458
|
params['X-Amz-Date'] = datetime
|
440
459
|
params['X-Amz-Expires'] = presigned_url_expiration(options, expiration, Time.strptime(datetime, "%Y%m%dT%H%M%S%Z")).to_s
|
@@ -447,6 +466,10 @@ module Aws
|
|
447
466
|
end
|
448
467
|
params['X-Amz-SignedHeaders'] = signed_headers(headers)
|
449
468
|
|
469
|
+
if @signing_algorithm == :sigv4a && @region
|
470
|
+
params['X-Amz-Region-Set'] = @region
|
471
|
+
end
|
472
|
+
|
450
473
|
params = params.map do |key, value|
|
451
474
|
"#{uri_escape(key)}=#{uri_escape(value)}"
|
452
475
|
end.join('&')
|
@@ -458,13 +481,23 @@ module Aws
|
|
458
481
|
end
|
459
482
|
|
460
483
|
creq = canonical_request(http_method, url, headers, content_sha256)
|
461
|
-
sts = string_to_sign(datetime, creq)
|
462
|
-
|
484
|
+
sts = string_to_sign(datetime, creq, algorithm)
|
485
|
+
signature =
|
486
|
+
if @signing_algorithm == :sigv4a
|
487
|
+
asymmetric_signature(creds, sts)
|
488
|
+
else
|
489
|
+
signature(creds.secret_access_key, date, sts)
|
490
|
+
end
|
491
|
+
url.query += '&X-Amz-Signature=' + signature
|
463
492
|
url
|
464
493
|
end
|
465
494
|
|
466
495
|
private
|
467
496
|
|
497
|
+
def sts_algorithm
|
498
|
+
@signing_algorithm == :sigv4a ? 'AWS4-ECDSA-P256-SHA256' : 'AWS4-HMAC-SHA256'
|
499
|
+
end
|
500
|
+
|
468
501
|
def canonical_request(http_method, url, headers, content_sha256)
|
469
502
|
[
|
470
503
|
http_method,
|
@@ -476,9 +509,9 @@ module Aws
|
|
476
509
|
].join("\n")
|
477
510
|
end
|
478
511
|
|
479
|
-
def string_to_sign(datetime, canonical_request)
|
512
|
+
def string_to_sign(datetime, canonical_request, algorithm)
|
480
513
|
[
|
481
|
-
|
514
|
+
algorithm,
|
482
515
|
datetime,
|
483
516
|
credential_scope(datetime[0,8]),
|
484
517
|
sha256_hexdigest(canonical_request),
|
@@ -511,10 +544,10 @@ module Aws
|
|
511
544
|
def credential_scope(date)
|
512
545
|
[
|
513
546
|
date,
|
514
|
-
@region,
|
547
|
+
(@region unless @signing_algorithm == :sigv4a),
|
515
548
|
@service,
|
516
|
-
'aws4_request'
|
517
|
-
].join('/')
|
549
|
+
'aws4_request'
|
550
|
+
].compact.join('/')
|
518
551
|
end
|
519
552
|
|
520
553
|
def credential(credentials, date)
|
@@ -529,6 +562,16 @@ module Aws
|
|
529
562
|
hexhmac(k_credentials, string_to_sign)
|
530
563
|
end
|
531
564
|
|
565
|
+
def asymmetric_signature(creds, string_to_sign)
|
566
|
+
ec, _ = Aws::Sigv4::AsymmetricCredentials.derive_asymmetric_key(
|
567
|
+
creds.access_key_id, creds.secret_access_key
|
568
|
+
)
|
569
|
+
sts_digest = OpenSSL::Digest::SHA256.digest(string_to_sign)
|
570
|
+
s = ec.dsa_sign_asn1(sts_digest)
|
571
|
+
|
572
|
+
Digest.hexencode(s)
|
573
|
+
end
|
574
|
+
|
532
575
|
# Comparing to original signature v4 algorithm,
|
533
576
|
# returned signature is a binary string instread of
|
534
577
|
# hex-encoded string. (Since ':chunk-signature' requires
|
@@ -896,6 +939,18 @@ module Aws
|
|
896
939
|
end
|
897
940
|
end
|
898
941
|
|
942
|
+
# @api private
|
943
|
+
def normalize_path(uri)
|
944
|
+
normalized_path = Pathname.new(uri.path).cleanpath.to_s
|
945
|
+
# Pathname is probably not correct to use. Empty paths will
|
946
|
+
# resolve to "." and should be disregarded
|
947
|
+
normalized_path = '' if normalized_path == '.'
|
948
|
+
# Ensure trailing slashes are correctly preserved
|
949
|
+
if uri.path.end_with?('/') && !normalized_path.end_with?('/')
|
950
|
+
normalized_path << '/'
|
951
|
+
end
|
952
|
+
uri.path = normalized_path
|
953
|
+
end
|
899
954
|
end
|
900
955
|
end
|
901
956
|
end
|
data/lib/aws-sigv4.rb
CHANGED
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
|
+
version: 1.9.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
|
-
date:
|
11
|
+
date: 2024-07-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: aws-eventstream
|
@@ -32,7 +32,7 @@ dependencies:
|
|
32
32
|
version: 1.0.2
|
33
33
|
description: Amazon Web Services Signature Version 4 signing library. Generates sigv4
|
34
34
|
signature for HTTP requests.
|
35
|
-
email:
|
35
|
+
email:
|
36
36
|
executables: []
|
37
37
|
extensions: []
|
38
38
|
extra_rdoc_files: []
|
@@ -41,6 +41,7 @@ files:
|
|
41
41
|
- LICENSE.txt
|
42
42
|
- VERSION
|
43
43
|
- lib/aws-sigv4.rb
|
44
|
+
- lib/aws-sigv4/asymmetric_credentials.rb
|
44
45
|
- lib/aws-sigv4/credentials.rb
|
45
46
|
- lib/aws-sigv4/errors.rb
|
46
47
|
- lib/aws-sigv4/request.rb
|
@@ -52,7 +53,7 @@ licenses:
|
|
52
53
|
metadata:
|
53
54
|
source_code_uri: https://github.com/aws/aws-sdk-ruby/tree/version-3/gems/aws-sigv4
|
54
55
|
changelog_uri: https://github.com/aws/aws-sdk-ruby/tree/version-3/gems/aws-sigv4/CHANGELOG.md
|
55
|
-
post_install_message:
|
56
|
+
post_install_message:
|
56
57
|
rdoc_options: []
|
57
58
|
require_paths:
|
58
59
|
- lib
|
@@ -67,8 +68,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
67
68
|
- !ruby/object:Gem::Version
|
68
69
|
version: '0'
|
69
70
|
requirements: []
|
70
|
-
rubygems_version: 3.
|
71
|
-
signing_key:
|
71
|
+
rubygems_version: 3.4.10
|
72
|
+
signing_key:
|
72
73
|
specification_version: 4
|
73
74
|
summary: AWS Signature Version 4 library.
|
74
75
|
test_files: []
|