Class: Miasma::Contrib::AwsApiCore::SignatureV4

Inherits:
Signature
  • Object
show all
Includes:
Bogo::Logger::Helpers
Defined in:
lib/miasma/contrib/aws.rb

Overview

AWS signature version 4

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods inherited from Signature

#safe_escape

Constructor Details

#initialize(access_key, secret_key, region, service) ⇒ self

Create new signature generator

Parameters:

  • access_key (String)
  • secret_key (String)
  • region (String)
  • service (String)


178
179
180
181
182
183
# File 'lib/miasma/contrib/aws.rb', line 178

def initialize(access_key, secret_key, region, service)
  @hmac = Hmac.new("sha256", secret_key)
  @access_key = access_key
  @region = region
  @service = service
end

Instance Attribute Details

#access_keyString (readonly)

Returns access key.

Returns:

  • (String)

    access key



165
166
167
# File 'lib/miasma/contrib/aws.rb', line 165

def access_key
  @access_key
end

#hmacHmac (readonly)

Returns:



163
164
165
# File 'lib/miasma/contrib/aws.rb', line 163

def hmac
  @hmac
end

#regionString (readonly)

Returns region.

Returns:

  • (String)

    region



167
168
169
# File 'lib/miasma/contrib/aws.rb', line 167

def region
  @region
end

#serviceString (readonly)

Returns service.

Returns:

  • (String)

    service



169
170
171
# File 'lib/miasma/contrib/aws.rb', line 169

def service
  @service
end

Instance Method Details

#algorithmString

Returns signature algorithm.

Returns:

  • (String)

    signature algorithm



263
264
265
# File 'lib/miasma/contrib/aws.rb', line 263

def algorithm
  "AWS4-HMAC-SHA256"
end

#build_canonical_request(http_method, path, opts) ⇒ String

Build the canonical request string used for signing

Parameters:

  • http_method (Symbol)

    HTTP request method

  • path (String)

    request path

  • opts (Hash)

    request options

Returns:

  • (String)

    canonical request string



291
292
293
294
295
296
297
298
299
300
301
302
303
# File 'lib/miasma/contrib/aws.rb', line 291

def build_canonical_request(http_method, path, opts)
  unless path.start_with?("/")
    path = "/#{path}"
  end
  [
    http_method.to_s.upcase,
    path,
    canonical_query(opts[:params]),
    canonical_headers(opts[:headers]),
    signed_headers(opts[:headers]),
    canonical_payload(opts),
  ].join("\n")
end

#canonical_headers(headers) ⇒ String

Build the canonical header string used for signing

Parameters:

  • headers (Hash)

    request headers

Returns:

  • (String)

    canonical headers string



321
322
323
324
325
326
327
# File 'lib/miasma/contrib/aws.rb', line 321

def canonical_headers(headers)
  headers ||= {}
  headers = Hash[headers.sort_by(&:first)]
  headers.map do |key, value|
    [key.downcase, value.chomp].join(":")
  end.join("\n") << "\n"
end

#canonical_payload(options) ⇒ String

Build the canonical payload string used for signing

Parameters:

  • options (Hash)

    request options

Returns:

  • (String)

    body checksum



343
344
345
346
347
348
349
350
351
352
353
354
355
# File 'lib/miasma/contrib/aws.rb', line 343

def canonical_payload(options)
  body = options.fetch(:body, "")
  if options[:json]
    body = MultiJson.dump(options[:json])
  elsif options[:form]
    body = URI.encode_www_form(options[:form])
  end
  if body == "UNSIGNED-PAYLOAD"
    body
  else
    hmac.hexdigest_of(body)
  end
end

#canonical_query(params) ⇒ String

Build the canonical query string used for signing

Parameters:

  • params (Hash)

    query params

Returns:

  • (String)

    canonical query string



309
310
311
312
313
314
315
# File 'lib/miasma/contrib/aws.rb', line 309

def canonical_query(params)
  params ||= {}
  params = Hash[params.sort_by(&:first)]
  query = params.map do |key, value|
    "#{safe_escape(key)}=#{safe_escape(value)}"
  end.join("&")
end

#credential_scopeString

Returns credential scope for request.

Returns:

  • (String)

    credential scope for request



268
269
270
271
272
273
274
275
# File 'lib/miasma/contrib/aws.rb', line 268

def credential_scope
  [
    Time.now.utc.strftime("%Y%m%d"),
    region,
    service,
    "aws4_request",
  ].join("/")
end

#generate(http_method, path, opts) ⇒ String

Generate the signature string for AUTH

Parameters:

  • http_method (Symbol)

    HTTP request method

  • path (String)

    request path

  • opts (Hash)

    request options

Returns:

  • (String)

    signature



191
192
193
194
195
# File 'lib/miasma/contrib/aws.rb', line 191

def generate(http_method, path, opts)
  signature = generate_signature(http_method, path, opts)
  "#{algorithm} Credential=#{access_key}/#{credential_scope}, " \
  "SignedHeaders=#{signed_headers(opts[:headers])}, Signature=#{signature}"
end

#generate_signature(http_method, path, opts) ⇒ String

Generate the signature

Parameters:

  • http_method (Symbol)

    HTTP request method

  • path (String)

    request path

  • opts (Hash)

    request options

Returns:

  • (String)

    signature



226
227
228
229
230
231
232
233
234
235
236
237
# File 'lib/miasma/contrib/aws.rb', line 226

def generate_signature(http_method, path, opts)
  to_sign = [
    algorithm,
    opts.to_smash.fetch(:headers, "X-Amz-Date", AwsApiCore.time_iso8601),
    credential_scope,
    hashed_canonical_request(
      can_req = build_canonical_request(http_method, path, opts)
    ),
  ].join("\n")
  logger.debug("generating signature for `#{to_sign.inspect}`")
  signature = sign_request(to_sign)
end

#generate_url(http_method, path, opts) ⇒ String

Generate URL with signed params

Parameters:

  • http_method (Symbol)

    HTTP request method

  • path (String)

    request path

  • opts (Hash)

    request options

Returns:

  • (String)

    signature



203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
# File 'lib/miasma/contrib/aws.rb', line 203

def generate_url(http_method, path, opts)
  opts[:params].merge!(
    Smash.new(
      "X-Amz-SignedHeaders" => signed_headers(opts[:headers]),
      "X-Amz-Algorithm" => algorithm,
      "X-Amz-Credential" => "#{access_key}/#{credential_scope}",
    )
  )
  signature = generate_signature(
    http_method, path,
    opts.merge(:body => "UNSIGNED-PAYLOAD")
  )
  params = opts[:params].merge("X-Amz-Signature" => signature)
  logger.debug("url generation parameters `#{params.inspect}`")
  "https://#{opts[:headers]["Host"]}/#{path}?#{canonical_query(params)}"
end

#hashed_canonical_request(request) ⇒ String

Generate the hash of the canonical request

Parameters:

  • request (String)

    canonical request string

Returns:

  • (String)

    hashed canonical request



281
282
283
# File 'lib/miasma/contrib/aws.rb', line 281

def hashed_canonical_request(request)
  hmac.hexdigest_of(request)
end

#sign_request(request) ⇒ String

Sign the request

Parameters:

  • request (String)

    request to sign

Returns:

  • (String)

    signature



243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
# File 'lib/miasma/contrib/aws.rb', line 243

def sign_request(request)
  key = hmac.sign(
    "aws4_request",
    hmac.sign(
      service,
      hmac.sign(
        region,
        hmac.sign(
          Time.now.utc.strftime("%Y%m%d"),
          "AWS4#{hmac.key}"
        )
      )
    )
  )
  signature = hmac.hex_sign(request, key)
  logger.debug("generated signature `#{signature.inspect}`")
  signature
end

#signed_headers(headers) ⇒ String

List of headers included in signature

Parameters:

  • headers (Hash)

    request headers

Returns:

  • (String)

    header list



333
334
335
336
337
# File 'lib/miasma/contrib/aws.rb', line 333

def signed_headers(headers)
  headers ||= {}
  headers.sort_by(&:first).map(&:first).
    map(&:downcase).join(";")
end