aws-sigv4 1.8.0 → 1.9.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 +10 -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 +82 -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: d6c968ea3d1cff2c3e6ff056a38658ce9af6f2f9b3d5fce948003a063c1f785e
|
4
|
+
data.tar.gz: '008ac56a37950824779768b8e3e942a711a0cae225231e9f9897e0426a18d121'
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 49dfbb860585de3ca7f1f84e3ff2fa059025098fe3a0baf4ab4c4fd1ff6bdb4dab85b17b040ebc7fa5db743d1f5da9a4b0975187d2bf60aabfc29e9674b22ee3
|
7
|
+
data.tar.gz: 315d89e1c67bfc3938f267dcf50e47842f7cac514b07df07cc642be2bc99fbc55dd7256f564868c6f3e61ee125aa71d26a98af01c734a924aaa0ae5ac8e084e4
|
data/CHANGELOG.md
CHANGED
@@ -1,6 +1,16 @@
|
|
1
1
|
Unreleased Changes
|
2
2
|
------------------
|
3
3
|
|
4
|
+
1.9.1 (2024-07-29)
|
5
|
+
------------------
|
6
|
+
|
7
|
+
* Issue - Add missing require of `pathname` to `Signer`.
|
8
|
+
|
9
|
+
1.9.0 (2024-07-23)
|
10
|
+
------------------
|
11
|
+
|
12
|
+
* Feature - Support `sigv4a` signing algorithm without `aws-crt`.
|
13
|
+
|
4
14
|
1.8.0 (2023-11-28)
|
5
15
|
------------------
|
6
16
|
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
1.
|
1
|
+
1.9.1
|
@@ -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
@@ -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
|
@@ -84,14 +85,16 @@ module Aws
|
|
84
85
|
|
85
86
|
# @overload initialize(service:, region:, access_key_id:, secret_access_key:, session_token:nil, **options)
|
86
87
|
# @param [String] :service The service signing name, e.g. 's3'.
|
87
|
-
# @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.
|
88
90
|
# @param [String] :access_key_id
|
89
91
|
# @param [String] :secret_access_key
|
90
92
|
# @param [String] :session_token (nil)
|
91
93
|
#
|
92
94
|
# @overload initialize(service:, region:, credentials:, **options)
|
93
95
|
# @param [String] :service The service signing name, e.g. 's3'.
|
94
|
-
# @param [String] :region The region name, e.g. 'us-east-1'.
|
96
|
+
# @param [String] :region The region name, e.g. 'us-east-1'. When signing
|
97
|
+
# with sigv4a, this should be a comma separated list of regions.
|
95
98
|
# @param [Credentials] :credentials Any object that responds to the following
|
96
99
|
# methods:
|
97
100
|
#
|
@@ -102,7 +105,8 @@ module Aws
|
|
102
105
|
#
|
103
106
|
# @overload initialize(service:, region:, credentials_provider:, **options)
|
104
107
|
# @param [String] :service The service signing name, e.g. 's3'.
|
105
|
-
# @param [String] :region The region name, e.g. 'us-east-1'.
|
108
|
+
# @param [String] :region The region name, e.g. 'us-east-1'. When signing
|
109
|
+
# with sigv4a, this should be a comma separated list of regions.
|
106
110
|
# @param [#credentials] :credentials_provider An object that responds
|
107
111
|
# to `#credentials`, returning an object that responds to the following
|
108
112
|
# methods:
|
@@ -127,8 +131,7 @@ module Aws
|
|
127
131
|
# every other AWS service as of late 2016.
|
128
132
|
#
|
129
133
|
# @option options [Symbol] :signing_algorithm (:sigv4) The
|
130
|
-
# algorithm to use for signing.
|
131
|
-
# `aws-crt` is available.
|
134
|
+
# algorithm to use for signing.
|
132
135
|
#
|
133
136
|
# @option options [Boolean] :omit_session_token (false)
|
134
137
|
# (Supported only when `aws-crt` is available) If `true`,
|
@@ -136,8 +139,8 @@ module Aws
|
|
136
139
|
# but is treated as "unsigned" and does not contribute
|
137
140
|
# to the authorization signature.
|
138
141
|
#
|
139
|
-
# @option options [Boolean] :normalize_path (true)
|
140
|
-
#
|
142
|
+
# @option options [Boolean] :normalize_path (true) When `true`, the
|
143
|
+
# uri paths will be normalized when building the canonical request.
|
141
144
|
def initialize(options = {})
|
142
145
|
@service = extract_service(options)
|
143
146
|
@region = extract_region(options)
|
@@ -152,12 +155,6 @@ module Aws
|
|
152
155
|
@normalize_path = options.fetch(:normalize_path, true)
|
153
156
|
@omit_session_token = options.fetch(:omit_session_token, false)
|
154
157
|
|
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
158
|
if @signing_algorithm == 'sigv4-s3express'.to_sym &&
|
162
159
|
Signer.use_crt? && Aws::Crt::GEM_VERSION <= '0.1.9'
|
163
160
|
raise ArgumentError,
|
@@ -246,6 +243,7 @@ module Aws
|
|
246
243
|
|
247
244
|
http_method = extract_http_method(request)
|
248
245
|
url = extract_url(request)
|
246
|
+
Signer.normalize_path(url) if @normalize_path
|
249
247
|
headers = downcase_headers(request[:headers])
|
250
248
|
|
251
249
|
datetime = headers['x-amz-date']
|
@@ -258,7 +256,7 @@ module Aws
|
|
258
256
|
sigv4_headers = {}
|
259
257
|
sigv4_headers['host'] = headers['host'] || host(url)
|
260
258
|
sigv4_headers['x-amz-date'] = datetime
|
261
|
-
if creds.session_token
|
259
|
+
if creds.session_token && !@omit_session_token
|
262
260
|
if @signing_algorithm == 'sigv4-s3express'.to_sym
|
263
261
|
sigv4_headers['x-amz-s3session-token'] = creds.session_token
|
264
262
|
else
|
@@ -268,26 +266,45 @@ module Aws
|
|
268
266
|
|
269
267
|
sigv4_headers['x-amz-content-sha256'] ||= content_sha256 if @apply_checksum_header
|
270
268
|
|
269
|
+
if @signing_algorithm == :sigv4a && @region && !@region.empty?
|
270
|
+
sigv4_headers['x-amz-region-set'] = @region
|
271
|
+
end
|
271
272
|
headers = headers.merge(sigv4_headers) # merge so we do not modify given headers hash
|
272
273
|
|
274
|
+
algorithm = sts_algorithm
|
275
|
+
|
273
276
|
# compute signature parts
|
274
277
|
creq = canonical_request(http_method, url, headers, content_sha256)
|
275
|
-
sts = string_to_sign(datetime, creq)
|
276
|
-
|
278
|
+
sts = string_to_sign(datetime, creq, algorithm)
|
279
|
+
|
280
|
+
sig =
|
281
|
+
if @signing_algorithm == :sigv4a
|
282
|
+
asymmetric_signature(creds, sts)
|
283
|
+
else
|
284
|
+
signature(creds.secret_access_key, date, sts)
|
285
|
+
end
|
286
|
+
|
287
|
+
algorithm = sts_algorithm
|
277
288
|
|
278
289
|
# apply signature
|
279
290
|
sigv4_headers['authorization'] = [
|
280
|
-
"
|
291
|
+
"#{algorithm} Credential=#{credential(creds, date)}",
|
281
292
|
"SignedHeaders=#{signed_headers(headers)}",
|
282
293
|
"Signature=#{sig}",
|
283
294
|
].join(', ')
|
284
295
|
|
296
|
+
# skip signing the session token, but include it in the headers
|
297
|
+
if creds.session_token && @omit_session_token
|
298
|
+
sigv4_headers['x-amz-security-token'] = creds.session_token
|
299
|
+
end
|
300
|
+
|
285
301
|
# Returning the signature components.
|
286
302
|
Signature.new(
|
287
303
|
headers: sigv4_headers,
|
288
304
|
string_to_sign: sts,
|
289
305
|
canonical_request: creq,
|
290
|
-
content_sha256: content_sha256
|
306
|
+
content_sha256: content_sha256,
|
307
|
+
signature: sig
|
291
308
|
)
|
292
309
|
end
|
293
310
|
|
@@ -421,6 +438,7 @@ module Aws
|
|
421
438
|
|
422
439
|
http_method = extract_http_method(options)
|
423
440
|
url = extract_url(options)
|
441
|
+
Signer.normalize_path(url) if @normalize_path
|
424
442
|
|
425
443
|
headers = downcase_headers(options[:headers])
|
426
444
|
headers['host'] ||= host(url)
|
@@ -433,8 +451,10 @@ module Aws
|
|
433
451
|
content_sha256 ||= options[:body_digest]
|
434
452
|
content_sha256 ||= sha256_hexdigest(options[:body] || '')
|
435
453
|
|
454
|
+
algorithm = sts_algorithm
|
455
|
+
|
436
456
|
params = {}
|
437
|
-
params['X-Amz-Algorithm'] =
|
457
|
+
params['X-Amz-Algorithm'] = algorithm
|
438
458
|
params['X-Amz-Credential'] = credential(creds, date)
|
439
459
|
params['X-Amz-Date'] = datetime
|
440
460
|
params['X-Amz-Expires'] = presigned_url_expiration(options, expiration, Time.strptime(datetime, "%Y%m%dT%H%M%S%Z")).to_s
|
@@ -447,6 +467,10 @@ module Aws
|
|
447
467
|
end
|
448
468
|
params['X-Amz-SignedHeaders'] = signed_headers(headers)
|
449
469
|
|
470
|
+
if @signing_algorithm == :sigv4a && @region
|
471
|
+
params['X-Amz-Region-Set'] = @region
|
472
|
+
end
|
473
|
+
|
450
474
|
params = params.map do |key, value|
|
451
475
|
"#{uri_escape(key)}=#{uri_escape(value)}"
|
452
476
|
end.join('&')
|
@@ -458,13 +482,23 @@ module Aws
|
|
458
482
|
end
|
459
483
|
|
460
484
|
creq = canonical_request(http_method, url, headers, content_sha256)
|
461
|
-
sts = string_to_sign(datetime, creq)
|
462
|
-
|
485
|
+
sts = string_to_sign(datetime, creq, algorithm)
|
486
|
+
signature =
|
487
|
+
if @signing_algorithm == :sigv4a
|
488
|
+
asymmetric_signature(creds, sts)
|
489
|
+
else
|
490
|
+
signature(creds.secret_access_key, date, sts)
|
491
|
+
end
|
492
|
+
url.query += '&X-Amz-Signature=' + signature
|
463
493
|
url
|
464
494
|
end
|
465
495
|
|
466
496
|
private
|
467
497
|
|
498
|
+
def sts_algorithm
|
499
|
+
@signing_algorithm == :sigv4a ? 'AWS4-ECDSA-P256-SHA256' : 'AWS4-HMAC-SHA256'
|
500
|
+
end
|
501
|
+
|
468
502
|
def canonical_request(http_method, url, headers, content_sha256)
|
469
503
|
[
|
470
504
|
http_method,
|
@@ -476,9 +510,9 @@ module Aws
|
|
476
510
|
].join("\n")
|
477
511
|
end
|
478
512
|
|
479
|
-
def string_to_sign(datetime, canonical_request)
|
513
|
+
def string_to_sign(datetime, canonical_request, algorithm)
|
480
514
|
[
|
481
|
-
|
515
|
+
algorithm,
|
482
516
|
datetime,
|
483
517
|
credential_scope(datetime[0,8]),
|
484
518
|
sha256_hexdigest(canonical_request),
|
@@ -511,10 +545,10 @@ module Aws
|
|
511
545
|
def credential_scope(date)
|
512
546
|
[
|
513
547
|
date,
|
514
|
-
@region,
|
548
|
+
(@region unless @signing_algorithm == :sigv4a),
|
515
549
|
@service,
|
516
|
-
'aws4_request'
|
517
|
-
].join('/')
|
550
|
+
'aws4_request'
|
551
|
+
].compact.join('/')
|
518
552
|
end
|
519
553
|
|
520
554
|
def credential(credentials, date)
|
@@ -529,6 +563,16 @@ module Aws
|
|
529
563
|
hexhmac(k_credentials, string_to_sign)
|
530
564
|
end
|
531
565
|
|
566
|
+
def asymmetric_signature(creds, string_to_sign)
|
567
|
+
ec, _ = Aws::Sigv4::AsymmetricCredentials.derive_asymmetric_key(
|
568
|
+
creds.access_key_id, creds.secret_access_key
|
569
|
+
)
|
570
|
+
sts_digest = OpenSSL::Digest::SHA256.digest(string_to_sign)
|
571
|
+
s = ec.dsa_sign_asn1(sts_digest)
|
572
|
+
|
573
|
+
Digest.hexencode(s)
|
574
|
+
end
|
575
|
+
|
532
576
|
# Comparing to original signature v4 algorithm,
|
533
577
|
# returned signature is a binary string instread of
|
534
578
|
# hex-encoded string. (Since ':chunk-signature' requires
|
@@ -896,6 +940,18 @@ module Aws
|
|
896
940
|
end
|
897
941
|
end
|
898
942
|
|
943
|
+
# @api private
|
944
|
+
def normalize_path(uri)
|
945
|
+
normalized_path = Pathname.new(uri.path).cleanpath.to_s
|
946
|
+
# Pathname is probably not correct to use. Empty paths will
|
947
|
+
# resolve to "." and should be disregarded
|
948
|
+
normalized_path = '' if normalized_path == '.'
|
949
|
+
# Ensure trailing slashes are correctly preserved
|
950
|
+
if uri.path.end_with?('/') && !normalized_path.end_with?('/')
|
951
|
+
normalized_path << '/'
|
952
|
+
end
|
953
|
+
uri.path = normalized_path
|
954
|
+
end
|
899
955
|
end
|
900
956
|
end
|
901
957
|
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.1
|
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-29 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: []
|