module MAuth::Client::LocalAuthenticator

Private Instance Methods

euresource_escape(str) click to toggle source

Note: RFC 3986 (www.ietf.org/rfc/rfc3986.txt) reserves the forward slash “/”

and number sign "#" as component delimiters. Since these are valid URI components,
they are decoded back into characters here to avoid signature invalidation
# File lib/mauth/client/local_authenticator.rb, line 119
def euresource_escape(str)
  CGI.escape(str).gsub(/%2F|%23/, '%2F' => '/', '%23' => '#')
end
euresource_query_escape(str) click to toggle source

Euresource encodes keys and values of query params but does not encode the '=' that separates keys and values and the '&' that separate k/v pairs Euresource currently adds query parameters via the following method: www.rubydoc.info/gems/addressable/2.3.4/Addressable/URI#query_values=-instance_method

# File lib/mauth/client/local_authenticator.rb, line 127
def euresource_query_escape(str)
  CGI.escape(str).gsub(/%3D|%26/, '%3D' => '=', '%26' => '&')
end
retrieve_public_key(app_uuid) click to toggle source
# File lib/mauth/client/local_authenticator.rb, line 131
def retrieve_public_key(app_uuid)
  retrieve_security_token(app_uuid)['security_token']['public_key_str']
end
retrieve_security_token(app_uuid) click to toggle source
# File lib/mauth/client/local_authenticator.rb, line 135
def retrieve_security_token(app_uuid)
  security_token_cacher.get(app_uuid)
end
security_token_cacher() click to toggle source
# File lib/mauth/client/local_authenticator.rb, line 139
def security_token_cacher
  @security_token_cacher ||= SecurityTokenCacher.new(self)
end
signature_valid_v1!(object) click to toggle source
# File lib/mauth/client/local_authenticator.rb, line 13
def signature_valid_v1!(object)
  # We are in an unfortunate situation in which Euresource is percent-encoding parts of paths, but not
  # all of them.  In particular, Euresource is percent-encoding all special characters save for '/'.
  # Also, unfortunately, Nginx unencodes URIs before sending them off to served applications, though
  # other web servers (particularly those we typically use for local testing) do not.  The various forms
  # of the expected string to sign are meant to cover the main cases.
  # TODO:  Revisit and simplify this unfortunate situation.

  original_request_uri = object.attributes_for_signing[:request_url]

  # craft an expected string-to-sign without doing any percent-encoding
  expected_no_reencoding = object.string_to_sign_v1(time: object.x_mws_time, app_uuid: object.signature_app_uuid)

  # do a simple percent reencoding variant of the path
  object.attributes_for_signing[:request_url] = CGI.escape(original_request_uri.to_s)
  expected_for_percent_reencoding = object.string_to_sign_v1(time: object.x_mws_time, app_uuid: object.signature_app_uuid)

  # do a moderately complex Euresource-style reencoding of the path
  object.attributes_for_signing[:request_url] = euresource_escape(original_request_uri.to_s)
  expected_euresource_style_reencoding = object.string_to_sign_v1(time: object.x_mws_time, app_uuid: object.signature_app_uuid)

  # reset the object original request_uri, just in case we need it again
  object.attributes_for_signing[:request_url] = original_request_uri

  begin
    pubkey = OpenSSL::PKey::RSA.new(retrieve_public_key(object.signature_app_uuid))
    actual = pubkey.public_decrypt(Base64.decode64(object.signature))
  rescue OpenSSL::PKey::PKeyError => e
    msg = "Public key decryption of signature failed! #{e.class}: #{e.message}"
    log_inauthentic(object, msg)
    raise InauthenticError, msg
  end

  unless verify_signature_v1!(actual, expected_no_reencoding) ||
     verify_signature_v1!(actual, expected_euresource_style_reencoding) ||
     verify_signature_v1!(actual, expected_for_percent_reencoding)
    msg = "Signature verification failed for #{object.class}"
    log_inauthentic(object, msg)
    raise InauthenticError, msg
  end
end
signature_valid_v2!(object) click to toggle source
# File lib/mauth/client/local_authenticator.rb, line 59
def signature_valid_v2!(object)
  # We are in an unfortunate situation in which Euresource is percent-encoding parts of paths, but not
  # all of them.  In particular, Euresource is percent-encoding all special characters save for '/'.
  # Also, unfortunately, Nginx unencodes URIs before sending them off to served applications, though
  # other web servers (particularly those we typically use for local testing) do not.  The various forms
  # of the expected string to sign are meant to cover the main cases.
  # TODO:  Revisit and simplify this unfortunate situation.

  original_request_uri = object.attributes_for_signing[:request_url]
  original_query_string = object.attributes_for_signing[:query_string]

  # craft an expected string-to-sign without doing any percent-encoding
  expected_no_reencoding = object.string_to_sign_v2(
    time: object.mcc_time,
    app_uuid: object.signature_app_uuid
  )

  # do a simple percent reencoding variant of the path
  expected_for_percent_reencoding = object.string_to_sign_v2(
    time: object.mcc_time,
    app_uuid: object.signature_app_uuid,
    request_url: CGI.escape(original_request_uri.to_s),
    query_string: CGI.escape(original_query_string.to_s)
  )

  # do a moderately complex Euresource-style reencoding of the path
  expected_euresource_style_reencoding = object.string_to_sign_v2(
    time: object.mcc_time,
    app_uuid: object.signature_app_uuid,
    request_url: euresource_escape(original_request_uri.to_s),
    query_string: euresource_query_escape(original_query_string.to_s)
  )

  pubkey = OpenSSL::PKey::RSA.new(retrieve_public_key(object.signature_app_uuid))
  actual = Base64.decode64(object.signature)

  unless verify_signature_v2!(object, actual, pubkey, expected_no_reencoding) ||
     verify_signature_v2!(object, actual, pubkey, expected_euresource_style_reencoding) ||
     verify_signature_v2!(object, actual, pubkey, expected_for_percent_reencoding)
    msg = "Signature inauthentic for #{object.class}"
    log_inauthentic(object, msg)
    raise InauthenticError, msg
  end
end
verify_signature_v1!(actual, expected_str_to_sign) click to toggle source
# File lib/mauth/client/local_authenticator.rb, line 55
def verify_signature_v1!(actual, expected_str_to_sign)
  actual == Digest::SHA512.hexdigest(expected_str_to_sign)
end
verify_signature_v2!(object, actual, pubkey, expected_str_to_sign) click to toggle source
# File lib/mauth/client/local_authenticator.rb, line 104
def verify_signature_v2!(object, actual, pubkey, expected_str_to_sign)
  pubkey.verify(
    MAuth::Client::SIGNING_DIGEST,
    actual,
    expected_str_to_sign
  )
rescue OpenSSL::PKey::PKeyError => e
  msg = "RSA verification of signature failed! #{e.class}: #{e.message}"
  log_inauthentic(object, msg)
  raise InauthenticError, msg
end