aws-sigv4 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: dc5dab371e96d81659b31a8f240fac3f0181e1b1
4
+ data.tar.gz: fa9c9cce2b176379585923c0345890756f930e78
5
+ SHA512:
6
+ metadata.gz: cdad04342ef2a0e2e8d1016af4d7e50daf13bc892981f7629c7fb444bf86c599ad6c9657e377176c66ebddaea8a73b211c13e05c63ae38da9cf7f4cec86d70c7
7
+ data.tar.gz: af5088ab2930b07050ef3ab12c7de87671f54f3d8bcecc38a1fdde49e1df69f43cb11a80c402caf7257f5156b2eec935d35bcab427578f979ee8e6185d5bb1c0
@@ -0,0 +1,4 @@
1
+ require_relative 'aws-sigv4/credentials'
2
+ require_relative 'aws-sigv4/errors'
3
+ require_relative 'aws-sigv4/signature'
4
+ require_relative 'aws-sigv4/signer'
@@ -0,0 +1,59 @@
1
+ module Aws
2
+ module Sigv4
3
+ # Users that wish to configure static credentials can use the
4
+ # `:access_key_id` and `:secret_access_key` constructor options.
5
+ # @api private
6
+ class Credentials
7
+
8
+ # @option options [required, String] :access_key_id
9
+ # @option options [required, String] :secret_access_key
10
+ # @option options [String, nil] :session_token (nil)
11
+ def initialize(options = {})
12
+ if options[:access_key_id] && options[:secret_access_key]
13
+ @access_key_id = options[:access_key_id]
14
+ @secret_access_key = options[:secret_access_key]
15
+ @session_token = options[:session_token]
16
+ else
17
+ msg = "expected both :access_key_id and :secret_access_key options"
18
+ raise ArgumentError, msg
19
+ end
20
+ end
21
+
22
+ # @return [String]
23
+ attr_reader :access_key_id
24
+
25
+ # @return [String]
26
+ attr_reader :secret_access_key
27
+
28
+ # @return [String, nil]
29
+ attr_reader :session_token
30
+
31
+ # @return [Boolean]
32
+ def set?
33
+ !!(access_key_id && secret_access_key)
34
+ end
35
+
36
+ end
37
+
38
+ # Users that wish to configure static credentials can use the
39
+ # `:access_key_id` and `:secret_access_key` constructor options.
40
+ # @api private
41
+ class StaticCredentialsProvider
42
+
43
+ # @option options [Credentials] :credentials
44
+ # @option options [String] :access_key_id
45
+ # @option options [String] :secret_access_key
46
+ # @option options [String] :session_token (nil)
47
+ def initialize(options = {})
48
+ @credentials = options[:credentials] ?
49
+ options[:credentials] :
50
+ Credentials.new(options)
51
+ end
52
+
53
+ # @return [Credentials]
54
+ attr_reader :credentials
55
+
56
+ end
57
+
58
+ end
59
+ end
@@ -0,0 +1,25 @@
1
+ module Aws
2
+ module Sigv4
3
+ module Errors
4
+
5
+ class MissingCredentialsError < ArgumentError
6
+ def initialize(msg = nil)
7
+ super(msg || <<-MSG.strip)
8
+ missing credentials, provide credentials with one of the following options:
9
+ - :access_key_id and :secret_access_key
10
+ - :credentials
11
+ - :credentials_provider
12
+ MSG
13
+ end
14
+ end
15
+
16
+ class MissingRegionError < ArgumentError
17
+ def initialize(*args)
18
+ super("missing required option :region")
19
+ end
20
+ end
21
+
22
+ end
23
+ end
24
+ end
25
+
@@ -0,0 +1,63 @@
1
+ require 'uri'
2
+
3
+ module Aws
4
+ module Sigv4
5
+ class Request
6
+
7
+ # @option options [required, String] :http_method
8
+ # @option options [required, HTTP::URI, HTTPS::URI, String] :endpoint
9
+ # @option options [Hash<String,String>] :headers ({})
10
+ # @option options [String, IO] :body ('')
11
+ def initialize(options = {})
12
+ @http_method = nil
13
+ @endpoint = nil
14
+ @headers = {}
15
+ @body = ''
16
+ options.each_pair do |attr_name, attr_value|
17
+ send("#{attr_name}=", attr_value)
18
+ end
19
+ end
20
+
21
+ # @param [String] http_method One of 'GET', 'PUT', 'POST', 'DELETE', 'HEAD', or 'PATCH'
22
+ def http_method=(http_method)
23
+ @http_method = http_method
24
+ end
25
+
26
+ # @return [String] One of 'GET', 'PUT', 'POST', 'DELETE', 'HEAD', or 'PATCH'
27
+ def http_method
28
+ @http_method
29
+ end
30
+
31
+ # @param [String, HTTP::URI, HTTPS::URI] endpoint
32
+ def endpoint=(endpoint)
33
+ @endpoint = URI.parse(endpoint.to_s)
34
+ end
35
+
36
+ # @return [HTTP::URI, HTTPS::URI]
37
+ def endpoint
38
+ @endpoint
39
+ end
40
+
41
+ # @param [Hash] headers
42
+ def headers=(headers)
43
+ @headers = headers
44
+ end
45
+
46
+ # @return [Hash<String,String>]
47
+ def headers
48
+ @headers
49
+ end
50
+
51
+ # @param [String, IO] body
52
+ def body=(body)
53
+ @body = body
54
+ end
55
+
56
+ # @return [String, IO]
57
+ def body
58
+ @body
59
+ end
60
+
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,35 @@
1
+ module Aws
2
+ module Sigv4
3
+ class Signature
4
+
5
+ # @api private
6
+ def initialize(options)
7
+ options.each_pair do |attr_name, attr_value|
8
+ send("#{attr_name}=", attr_value)
9
+ end
10
+ end
11
+
12
+ # @return [Hash<String,String>] A hash of headers that should
13
+ # be applied to the HTTP request. Header keys are lower
14
+ # cased strings and may include the following:
15
+ #
16
+ # * 'host'
17
+ # * 'x-amz-date'
18
+ # * 'x-amz-security-token'
19
+ # * 'x-amz-content-sha256'
20
+ # * 'authorization'
21
+ #
22
+ attr_accessor :headers
23
+
24
+ # @return [String] For debugging purposes.
25
+ attr_accessor :canonical_request
26
+
27
+ # @return [String] For debugging purposes.
28
+ attr_accessor :string_to_sign
29
+
30
+ # @return [String] For debugging purposes.
31
+ attr_accessor :content_sha256
32
+
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,596 @@
1
+ require 'openssl'
2
+ require 'tempfile'
3
+ require 'time'
4
+ require 'uri'
5
+ require 'set'
6
+ require 'cgi'
7
+
8
+ module Aws
9
+ module Sigv4
10
+
11
+ # Utility class for creating AWS signature version 4 signature. This class
12
+ # provides two methods for generating signatures:
13
+ #
14
+ # * {#sign_request} - Computes a signature of the given request, returning
15
+ # the hash of headers that should be applied to the request.
16
+ #
17
+ # * {#presign_url} - Computes a presigned request with an expiration.
18
+ # By default, the body of this request is not signed and the request
19
+ # expires in 15 minutes.
20
+ #
21
+ # ## Configuration
22
+ #
23
+ # To use the signer, you need to specify the service, region, and credentials.
24
+ # The service name is normally the endpoint prefix to an AWS service. For
25
+ # example:
26
+ #
27
+ # ec2.us-west-1.amazonaws.com => ec2
28
+ #
29
+ # The region is normally the second portion of the endpoint, following
30
+ # the service name.
31
+ #
32
+ # ec2.us-west-1.amazonaws.com => us-west-1
33
+ #
34
+ # It is important to have the correct service and region name, or the
35
+ # signature will be invalid.
36
+ #
37
+ # ## Credentials
38
+ #
39
+ # The signer requires credentials. You can configure the signer
40
+ # with static credentials:
41
+ #
42
+ # signer = Aws::Sigv4::Signer.new(
43
+ # service: 's3',
44
+ # region: 'us-east-1',
45
+ # # static credentials
46
+ # access_key_id: 'akid',
47
+ # secret_access_key: 'secret'
48
+ # )
49
+ #
50
+ # You can also provide refreshing credentials via the `:credentials_provider`.
51
+ # If you are using the AWS SDK for Ruby, you can use any of the credential
52
+ # classes:
53
+ #
54
+ # signer = Aws::Sigv4::Signer.new(
55
+ # service: 's3',
56
+ # region: 'us-east-1',
57
+ # credentials_provider: Aws::InstanceProfileCredentials.new
58
+ # )
59
+ #
60
+ # Other AWS SDK for Ruby classes that can be provided via `:credentials_provider`:
61
+ #
62
+ # * `Aws::Credentials`
63
+ # * `Aws::SharedCredentials`
64
+ # * `Aws::InstanceProfileCredentials`
65
+ # * `Aws::AssumeRoleCredentials`
66
+ # * `Aws::ECSCredentials`
67
+ #
68
+ # A credential provider is any object that responds to `#credentials`
69
+ # returning another object that responds to `#access_key_id`, `#secret_access_key`,
70
+ # and `#session_token`.
71
+ #
72
+ class Signer
73
+
74
+ # @overload initialize(service:, region:, access_key_id:, secret_access_key:, session_token:nil, **options)
75
+ # @param [String] :service The service signing name, e.g. 's3'.
76
+ # @param [String] :region The region name, e.g. 'us-east-1'.
77
+ # @param [String] :access_key_id
78
+ # @param [String] :secret_access_key
79
+ # @param [String] :session_token (nil)
80
+ #
81
+ # @overload initialize(service:, region:, credentials:, **options)
82
+ # @param [String] :service The service signing name, e.g. 's3'.
83
+ # @param [String] :region The region name, e.g. 'us-east-1'.
84
+ # @param [Credentials] :credentials Any object that responds to the following
85
+ # methods:
86
+ #
87
+ # * `#access_key_id` => String
88
+ # * `#secret_access_key` => String
89
+ # * `#session_token` => String, nil
90
+ # * `#set?` => Boolean
91
+ #
92
+ # @overload initialize(service:, region:, credentials_provider:, **options)
93
+ # @param [String] :service The service signing name, e.g. 's3'.
94
+ # @param [String] :region The region name, e.g. 'us-east-1'.
95
+ # @param [#credentials] :credentials_provider An object that responds
96
+ # to `#credentials`, returning an object that responds to the following
97
+ # methods:
98
+ #
99
+ # * `#access_key_id` => String
100
+ # * `#secret_access_key` => String
101
+ # * `#session_token` => String, nil
102
+ # * `#set?` => Boolean
103
+ #
104
+ # @option options [Array<String>] :unsigned_headers ([]) A list of
105
+ # headers that should not be signed. This is useful when a proxy
106
+ # modifies headers, such as 'User-Agent', invalidating a signature.
107
+ #
108
+ # @option options [Boolean] :uri_escape_path (true) When `true`,
109
+ # the request URI path is uri-escaped as part of computing the canonical
110
+ # request string. This is required for every service, except Amazon S3,
111
+ # as of late 2016.
112
+ #
113
+ # @option options [Boolean] :apply_checksum_header (true) When `true`,
114
+ # the computed content checksum is returned in the hash of signature
115
+ # headers. This is required for AWS Glacier, and optional for
116
+ # every other AWS service as of late 2016.
117
+ #
118
+ def initialize(options = {})
119
+ @service = extract_service(options)
120
+ @region = extract_region(options)
121
+ @credentials_provider = extract_credentials_provider(options)
122
+ @unsigned_headers = Set.new((options.fetch(:unsigned_headers, [])).map(&:downcase))
123
+ @unsigned_headers << 'authorization'
124
+ [:uri_escape_path, :apply_checksum_header].each do |opt|
125
+ instance_variable_set("@#{opt}", options.key?(opt) ? !!options[:opt] : true)
126
+ end
127
+ end
128
+
129
+ # @return [String]
130
+ attr_reader :service
131
+
132
+ # @return [String]
133
+ attr_reader :region
134
+
135
+ # @return [#credentials] Returns an object that responds to
136
+ # `#credentials`, returning an object that responds to the following
137
+ # methods:
138
+ #
139
+ # * `#access_key_id` => String
140
+ # * `#secret_access_key` => String
141
+ # * `#session_token` => String, nil
142
+ # * `#set?` => Boolean
143
+ #
144
+ attr_reader :credentials_provider
145
+
146
+ # @return [Set<String>] Returns a set of header names that should not be signed.
147
+ # All header names have been downcased.
148
+ attr_reader :unsigned_headers
149
+
150
+ # @return [Boolean] When `true` the `x-amz-content-sha256` header will be signed and
151
+ # returned in the signature headers.
152
+ attr_reader :apply_checksum_header
153
+
154
+ # Computes a version 4 signature signature. Returns the resultant
155
+ # signature as a hash of headers to apply to your HTTP request. The given
156
+ # request is not modified.
157
+ #
158
+ # signature = signer.sign_request(
159
+ # http_method: 'PUT',
160
+ # url: 'https://domain.com',
161
+ # headers: {
162
+ # 'Abc' => 'xyz',
163
+ # },
164
+ # body: 'body' # String or IO object
165
+ # )
166
+ #
167
+ # # Apply the following hash of headers to your HTTP request
168
+ # signature.headers['Host']
169
+ # signature.headers['X-Amz-Date']
170
+ # signature.headers['X-Amz-Security-Token']
171
+ # signature.headers['X-Amz-Content-Sha256']
172
+ # signature.headers['Authorization']
173
+ #
174
+ # In addition to computing the signature headers, the canonicalized
175
+ # request, string to sign and content sha256 checksum are also available.
176
+ # These values are useful for debugging signature errors returned by AWS.
177
+ #
178
+ # signature.canonical_request #=> "..."
179
+ # signature.string_to_sign #=> "..."
180
+ # signature.content_sha256 #=> "..."
181
+ #
182
+ # @param [Hash] request
183
+ #
184
+ # @option request [required, String] :http_method One of
185
+ # 'GET', 'HEAD', 'PUT', 'POST', 'PATCH', or 'DELETE'
186
+ #
187
+ # @option request [required, String, URI::HTTPS, URI::HTTP] :url
188
+ # The request URI. Must be a valid HTTP or HTTPS URI.
189
+ #
190
+ # @option request [optional, Hash] :headers ({}) A hash of headers
191
+ # to sign. If the 'X-Amz-Content-Sha256' header is set, the `:body`
192
+ # is optional and will not be read.
193
+ #
194
+ # @option request [otpional, String, IO] :body ('') The HTTP request body.
195
+ # A sha256 checksum is computed of the body unless the
196
+ # 'X-Amz-Content-Sha256' header is set.
197
+ #
198
+ # @return [Signature] Return an instance of {Signature} that has
199
+ # a `#headers` method. The headers must be applied to your request.
200
+ #
201
+ def sign_request(request)
202
+
203
+ creds = get_credentials
204
+
205
+ http_method = extract_http_method(request)
206
+ url = extract_url(request)
207
+ headers = downcase_headers(request[:headers])
208
+
209
+ datetime = headers['x-amz-date']
210
+ datetime ||= Time.now.utc.strftime("%Y%m%dT%H%M%SZ")
211
+ date = datetime[0,8]
212
+
213
+ content_sha256 = headers['x-amz-content-sha256']
214
+ content_sha256 ||= sha256_hexdigest(request[:body] || '')
215
+
216
+ sigv4_headers = {}
217
+ sigv4_headers['host'] = host(url)
218
+ sigv4_headers['x-amz-date'] = datetime
219
+ sigv4_headers['x-amz-security-token'] = creds.session_token if creds.session_token
220
+ sigv4_headers['x-amz-content-sha256'] ||= content_sha256 if @apply_checksum_header
221
+
222
+ headers = headers.merge(sigv4_headers) # merge so we do not modify given headers hash
223
+
224
+ # compute signature parts
225
+ creq = canonical_request(http_method, url, headers, content_sha256)
226
+ sts = string_to_sign(datetime, creq)
227
+ sig = signature(creds.secret_access_key, date, sts)
228
+
229
+ # apply signature
230
+ sigv4_headers['authorization'] = [
231
+ "AWS4-HMAC-SHA256 Credential=#{credential(creds, date)}",
232
+ "SignedHeaders=#{signed_headers(headers)}",
233
+ "Signature=#{sig}",
234
+ ].join(', ')
235
+
236
+ # Returning the signature components.
237
+ Signature.new(
238
+ headers: sigv4_headers,
239
+ string_to_sign: sts,
240
+ canonical_request: creq,
241
+ content_sha256: content_sha256
242
+ )
243
+ end
244
+
245
+ # Signs a URL with query authentication. Using query parameters
246
+ # to authenticate requests is useful when you want to express a
247
+ # request entirely in a URL. This method is also referred as
248
+ # presigning a URL.
249
+ #
250
+ # See [Authenticating Requests: Using Query Parameters (AWS Signature Version 4)](http://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-query-string-auth.html) for more information.
251
+ #
252
+ # To generate a presigned URL, you must provide a HTTP URI and
253
+ # the http method.
254
+ #
255
+ # url = signer.presigned_url(
256
+ # http_method: 'GET',
257
+ # url: 'https://my-bucket.s3-us-east-1.amazonaws.com/key',
258
+ # expires_in: 60
259
+ # )
260
+ #
261
+ # By default, signatures are valid for 15 minutes. You can specify
262
+ # the number of seconds for the URL to expire in.
263
+ #
264
+ # url = signer.presigned_url(
265
+ # http_method: 'GET',
266
+ # url: 'https://my-bucket.s3-us-east-1.amazonaws.com/key',
267
+ # expires_in: 3600 # one hour
268
+ # )
269
+ #
270
+ # You can provide a hash of headers that you plan to send with the
271
+ # request. Every 'X-Amz-*' header you plan to send with the request
272
+ # **must** be provided, or the signature is invalid. Other headers
273
+ # are optional, but should be provided for security reasons.
274
+ #
275
+ # url = signer.presigned_url(
276
+ # http_method: 'PUT',
277
+ # url: 'https://my-bucket.s3-us-east-1.amazonaws.com/key',
278
+ # headers: {
279
+ # 'X-Amz-Meta-Custom' => 'metadata'
280
+ # }
281
+ # )
282
+ #
283
+ # @option options [required, String] :http_method The HTTP request method,
284
+ # e.g. 'GET', 'HEAD', 'PUT', 'POST', 'PATCH', or 'DELETE'.
285
+ #
286
+ # @option options [required, String, HTTPS::URI, HTTP::URI] :url
287
+ # The URI to sign.
288
+ #
289
+ # @option options [Hash] :headers ({}) Headers that should
290
+ # be signed and sent along with the request. All x-amz-*
291
+ # headers must be present during signing. Other
292
+ # headers are optional.
293
+ #
294
+ # @option options [Integer<Seconds>] :expires_in (900)
295
+ # How long the presigned URL should be valid for. Defaults
296
+ # to 15 minutes (900 seconds).
297
+ #
298
+ # @option options [optional, String, IO] :body
299
+ # If the `:body` is set, then a SHA256 hexdigest will be computed of the body.
300
+ # If `:body_digest` is set, this option is ignored. If neither are set, then
301
+ # the `:body_digest` will be computed of the empty string.
302
+ #
303
+ # @option options [optional, String] :body_digest
304
+ # The SHA256 hexdigest of the request body. If you wish to send the presigned
305
+ # request without signing the body, you can pass 'UNSIGNED-PAYLOAD' as the
306
+ # `:body_digest` in place of passing `:body`.
307
+ #
308
+ # @option options [Time] :time (Time.now) Time of the signature.
309
+ # You should only set this value for testing.
310
+ #
311
+ # @return [HTTPS::URI, HTTP::URI]
312
+ #
313
+ def presign_url(options)
314
+
315
+ creds = get_credentials
316
+
317
+ http_method = extract_http_method(options)
318
+ url = extract_url(options)
319
+
320
+ headers = downcase_headers(options[:headers])
321
+ headers['host'] = host(url)
322
+
323
+ datetime = headers['x-amz-date']
324
+ datetime ||= (options[:time] || Time.now).utc.strftime("%Y%m%dT%H%M%SZ")
325
+ date = datetime[0,8]
326
+
327
+ content_sha256 = headers['x-amz-content-sha256']
328
+ content_sha256 ||= options[:body_digest]
329
+ content_sha256 ||= sha256_hexdigest(options[:body] || '')
330
+
331
+ params = {}
332
+ params['X-Amz-Algorithm'] = 'AWS4-HMAC-SHA256'
333
+ params['X-Amz-Credential'] = credential(creds, date)
334
+ params['X-Amz-Date'] = datetime
335
+ params['X-Amz-Expires'] = extract_expires_in(options)
336
+ params['X-Amz-SignedHeaders'] = signed_headers(headers)
337
+ params['X-Amz-Security-Token'] = creds.session_token if creds.session_token
338
+
339
+ params = params.map do |key, value|
340
+ "#{uri_escape(key)}=#{uri_escape(value)}"
341
+ end.join('&')
342
+
343
+ if url.query
344
+ url.query += '&' + params
345
+ else
346
+ url.query = params
347
+ end
348
+
349
+ creq = canonical_request(http_method, url, headers, content_sha256)
350
+ sts = string_to_sign(datetime, creq)
351
+ url.query += '&X-Amz-Signature=' + signature(creds.secret_access_key, date, sts)
352
+ url
353
+ end
354
+
355
+ private
356
+
357
+ def canonical_request(http_method, url, headers, content_sha256)
358
+ [
359
+ http_method,
360
+ path(url),
361
+ normalized_querystring(url.query || ''),
362
+ canonical_headers(headers) + "\n",
363
+ signed_headers(headers),
364
+ content_sha256,
365
+ ].join("\n")
366
+ end
367
+
368
+ def string_to_sign(datetime, canonical_request)
369
+ [
370
+ 'AWS4-HMAC-SHA256',
371
+ datetime,
372
+ credential_scope(datetime[0,8]),
373
+ sha256_hexdigest(canonical_request),
374
+ ].join("\n")
375
+ end
376
+
377
+ def credential_scope(date)
378
+ [
379
+ date,
380
+ @region,
381
+ @service,
382
+ 'aws4_request',
383
+ ].join('/')
384
+ end
385
+
386
+ def credential(credentials, date)
387
+ "#{credentials.access_key_id}/#{credential_scope(date)}"
388
+ end
389
+
390
+ def signature(secret_access_key, date, string_to_sign)
391
+ k_date = hmac("AWS4" + secret_access_key, date)
392
+ k_region = hmac(k_date, @region)
393
+ k_service = hmac(k_region, @service)
394
+ k_credentials = hmac(k_service, 'aws4_request')
395
+ hexhmac(k_credentials, string_to_sign)
396
+ end
397
+
398
+ def path(url)
399
+ path = url.path
400
+ path = '/' if path == ''
401
+ if @uri_escape_path
402
+ uri_escape_path(path)
403
+ else
404
+ path
405
+ end
406
+ end
407
+
408
+ def normalized_querystring(querystring)
409
+ params = querystring.split('&')
410
+ params = params.map { |p| p.match(/=/) ? p : p + '=' }
411
+ # We have to sort by param name and preserve order of params that
412
+ # have the same name. Default sort <=> in JRuby will swap members
413
+ # occasionally when <=> is 0 (considered still sorted), but this
414
+ # causes our normalized query string to not match the sent querystring.
415
+ # When names match, we then sort by their original order
416
+ params = params.each.with_index.sort do |a, b|
417
+ a, a_offset = a
418
+ a_name = a.split('=')[0]
419
+ b, b_offset = b
420
+ b_name = b.split('=')[0]
421
+ if a_name == b_name
422
+ a_offset <=> b_offset
423
+ else
424
+ a_name <=> b_name
425
+ end
426
+ end.map(&:first).join('&')
427
+ end
428
+
429
+ def signed_headers(headers)
430
+ headers.inject([]) do |signed_headers, (header, _)|
431
+ if @unsigned_headers.include?(header)
432
+ signed_headers
433
+ else
434
+ signed_headers << header
435
+ end
436
+ end.sort.join(';')
437
+ end
438
+
439
+ def canonical_headers(headers)
440
+ headers = headers.inject([]) do |headers, (k,v)|
441
+ if @unsigned_headers.include?(k)
442
+ headers
443
+ else
444
+ headers << [k,v]
445
+ end
446
+ end
447
+ headers = headers.sort_by(&:first)
448
+ headers.map{|k,v| "#{k}:#{canonical_header_value(v.to_s)}" }.join("\n")
449
+ end
450
+
451
+ def canonical_header_value(value)
452
+ value.match(/^".*"$/) ? value : value.gsub(/\s+/, ' ').strip
453
+ end
454
+
455
+ def host(uri)
456
+ if standard_port?(uri)
457
+ uri.host
458
+ else
459
+ "#{uri.host}:#{uri.port}"
460
+ end
461
+ end
462
+
463
+ def standard_port?(uri)
464
+ (uri.scheme == 'http' && uri.port == 80) ||
465
+ (uri.scheme == 'https' && uri.port == 443)
466
+ end
467
+
468
+ # @param [File, Tempfile, IO#read, String] value
469
+ # @return [String<SHA256 Hexdigest>]
470
+ def sha256_hexdigest(value)
471
+ if (File === value || Tempfile === value) && !value.path.nil? && File.exist?(value.path)
472
+ OpenSSL::Digest::SHA256.file(value).hexdigest
473
+ elsif value.respond_to?(:read)
474
+ sha256 = OpenSSL::Digest::SHA256.new
475
+ while chunk = value.read(1024 * 1024) # 1MB
476
+ sha256.update(chunk)
477
+ end
478
+ value.rewind
479
+ sha256.hexdigest
480
+ else
481
+ OpenSSL::Digest::SHA256.hexdigest(value)
482
+ end
483
+ end
484
+
485
+ def hmac(key, value)
486
+ OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha256'), key, value)
487
+ end
488
+
489
+ def hexhmac(key, value)
490
+ OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha256'), key, value)
491
+ end
492
+
493
+ def extract_service(options)
494
+ if options[:service]
495
+ options[:service]
496
+ else
497
+ msg = "missing required option :service"
498
+ raise ArgumentError, msg
499
+ end
500
+ end
501
+
502
+ def extract_region(options)
503
+ if options[:region]
504
+ options[:region]
505
+ else
506
+ raise Errors::MissingRegionError
507
+ end
508
+ end
509
+
510
+ def extract_credentials_provider(options)
511
+ if options[:credentials_provider]
512
+ options[:credentials_provider]
513
+ elsif options.key?(:credentials) || options.key?(:access_key_id)
514
+ StaticCredentialsProvider.new(options)
515
+ else
516
+ raise Errors::MissingCredentialsError
517
+ end
518
+ end
519
+
520
+ def extract_http_method(request)
521
+ if request[:http_method]
522
+ request[:http_method].upcase
523
+ else
524
+ msg = "missing required option :http_method"
525
+ raise ArgumentError, msg
526
+ end
527
+ end
528
+
529
+ def extract_url(request)
530
+ if request[:url]
531
+ URI.parse(request[:url].to_s)
532
+ else
533
+ msg = "missing required option :url"
534
+ raise ArgumentError, msg
535
+ end
536
+ end
537
+
538
+ def downcase_headers(headers)
539
+ (headers || {}).to_hash.inject({}) do |hash, (key, value)|
540
+ hash[key.downcase] = value
541
+ hash
542
+ end
543
+ end
544
+
545
+ def extract_expires_in(options)
546
+ case options[:expires_in]
547
+ when nil then 900.to_s
548
+ when Integer then options[:expires_in].to_s
549
+ else
550
+ msg = "expected :expires_in to be a number of seconds"
551
+ raise ArgumentError, msg
552
+ end
553
+ end
554
+
555
+ def uri_escape(string)
556
+ self.class.uri_escape(string)
557
+ end
558
+
559
+ def uri_escape_path(string)
560
+ self.class.uri_escape_path(string)
561
+ end
562
+
563
+ def get_credentials
564
+ credentials = @credentials_provider.credentials
565
+ if credentials_set?(credentials)
566
+ credentials
567
+ else
568
+ msg = 'unable to sign request without credentials set'
569
+ raise Errors::MissingCredentialsError.new(msg)
570
+ end
571
+ end
572
+
573
+ def credentials_set?(credentials)
574
+ credentials.access_key_id && credentials.secret_access_key
575
+ end
576
+
577
+ class << self
578
+
579
+ # @api private
580
+ def uri_escape_path(path)
581
+ path.gsub(/[^\/]+/) { |part| uri_escape(part) }
582
+ end
583
+
584
+ # @api private
585
+ def uri_escape(string)
586
+ if string.nil?
587
+ nil
588
+ else
589
+ CGI.escape(string.encode('UTF-8')).gsub('+', '%20').gsub('%7E', '~')
590
+ end
591
+ end
592
+
593
+ end
594
+ end
595
+ end
596
+ end
metadata ADDED
@@ -0,0 +1,50 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: aws-sigv4
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Amazon Web Services
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-11-08 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Amazon Web Services Signature Version 4 signing ligrary. Generates sigv4
14
+ signature for HTTP requests.
15
+ email:
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - lib/aws-sigv4.rb
21
+ - lib/aws-sigv4/credentials.rb
22
+ - lib/aws-sigv4/errors.rb
23
+ - lib/aws-sigv4/request.rb
24
+ - lib/aws-sigv4/signature.rb
25
+ - lib/aws-sigv4/signer.rb
26
+ homepage: http://github.com/aws/aws-sdk-ruby
27
+ licenses:
28
+ - Apache-2.0
29
+ metadata: {}
30
+ post_install_message:
31
+ rdoc_options: []
32
+ require_paths:
33
+ - lib
34
+ required_ruby_version: !ruby/object:Gem::Requirement
35
+ requirements:
36
+ - - ">="
37
+ - !ruby/object:Gem::Version
38
+ version: '0'
39
+ required_rubygems_version: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
44
+ requirements: []
45
+ rubyforge_project:
46
+ rubygems_version: 2.6.4
47
+ signing_key:
48
+ specification_version: 4
49
+ summary: AWS Signature Version 4 library.
50
+ test_files: []