class Aws::SNS::MessageVerifier

A utility class that can be used to verify the authenticity of messages sent by Amazon SNS.

verifier = Aws::SNS::MessageVerifier.new

# returns true/false
verifier.authentic?(message_body)

# raises a Aws::SNS::MessageVerifier::VerificationError on failure
verifier.authenticate!(message_body)

You can re-use a single {MessageVerifier} instance to authenticate multiple SNS messages.

Constants

AWS_HOSTNAMES

@api private

SIGNABLE_KEYS

@api private

Public Class Methods

new(http_options = {}) click to toggle source

@param [Hash] http_options Supported options to be passed to Net::HTTP. @option http_options [String] :http_proxy A proxy to send

requests through.  Formatted like 'http://proxy.com:123'.
# File lib/aws-sdk-sns/message_verifier.rb, line 46
def initialize(http_options = {})
  @cached_pems = {}
  @http_proxy = http_options[:http_proxy]
end

Public Instance Methods

authentic?(message_body) click to toggle source

@param [String<JSON>] message_body @return [Boolean] Returns `true` if the given message has been

successfully verified. Returns `false` otherwise.
# File lib/aws-sdk-sns/message_verifier.rb, line 54
def authentic?(message_body)
  authenticate!(message_body)
rescue VerificationError
  false
end
authenticate!(message_body) click to toggle source

@param [String<JSON>] message_body @return [Boolean] Returns `true` when the given message has been

successfully verified.

@raise [VerificationError] Raised when the given message has failed

verification.
# File lib/aws-sdk-sns/message_verifier.rb, line 65
def authenticate!(message_body)
  msg = Json.load(message_body)
  msg = convert_lambda_msg(msg) if is_from_lambda(msg)
  if public_key(msg).verify(sha1, signature(msg), canonical_string(msg))
    true
  else
    msg = 'the authenticity of the message cannot be verified'
    raise VerificationError, msg
  end
end

Private Instance Methods

canonical_string(message) click to toggle source
# File lib/aws-sdk-sns/message_verifier.rb, line 99
def canonical_string(message)
  parts = []
  SIGNABLE_KEYS.each do |key|
    value = message[key]
    unless value.nil? or value.empty?
      parts << "#{key}\n#{value}\n"
    end
  end
  parts.join
end
convert_lambda_msg(message) click to toggle source
# File lib/aws-sdk-sns/message_verifier.rb, line 82
def convert_lambda_msg(message)
  cert_url = message.delete('SigningCertUrl')
  unsubscribe_url = message.delete('UnsubscribeUrl')

  message['SigningCertURL'] = cert_url
  message['UnsubscribeURL'] = unsubscribe_url
  message
end
download_pem(uri) click to toggle source
# File lib/aws-sdk-sns/message_verifier.rb, line 124
def download_pem(uri)
  verify_uri!(uri)
  https_get(uri)
end
http_proxy_parts() click to toggle source
# File lib/aws-sdk-sns/message_verifier.rb, line 178
def http_proxy_parts
  # empty string if not configured, URI parts return nil
  http_proxy = URI.parse(@http_proxy.to_s)
  [
    http_proxy.host,
    http_proxy.port,
    (http_proxy.user && CGI.unescape(http_proxy.user)),
    (http_proxy.password && CGI.unescape(http_proxy.password))
  ]
end
https_get(uri, failed_attempts = 0) click to toggle source
# File lib/aws-sdk-sns/message_verifier.rb, line 156
def https_get(uri, failed_attempts = 0)
  args = []
  args << uri.host
  args << uri.port
  args += http_proxy_parts
  http = Net::HTTP.new(*args.compact)
  http.use_ssl = true
  http.verify_mode = OpenSSL::SSL::VERIFY_PEER
  http.start
  resp = http.request(Net::HTTP::Get.new(uri.request_uri))
  http.finish
  if resp.code == '200'
    resp.body
  else
    raise VerificationError, resp.body
  end
rescue => error
  failed_attempts += 1
  retry if failed_attempts < 3
  raise VerificationError, error.message
end
is_from_lambda(message) click to toggle source
# File lib/aws-sdk-sns/message_verifier.rb, line 78
def is_from_lambda(message)
  message.key? 'SigningCertUrl'
end
pem(uri) click to toggle source
# File lib/aws-sdk-sns/message_verifier.rb, line 116
def pem(uri)
  if @cached_pems[uri.to_s]
    @cached_pems[uri.to_s]
  else
    @cached_pems[uri.to_s] = download_pem(uri)
  end
end
public_key(message) click to toggle source
# File lib/aws-sdk-sns/message_verifier.rb, line 110
def public_key(message)
  x509_url = URI.parse(message['SigningCertURL'])
  x509 = OpenSSL::X509::Certificate.new(pem(x509_url))
  OpenSSL::PKey::RSA.new(x509.public_key)
end
sha1() click to toggle source
# File lib/aws-sdk-sns/message_verifier.rb, line 91
def sha1
  OpenSSL::Digest::SHA1.new
end
signature(message) click to toggle source
# File lib/aws-sdk-sns/message_verifier.rb, line 95
def signature(message)
  Base64.decode64(message['Signature'])
end
verify_hosted_by_aws!(uri) click to toggle source
# File lib/aws-sdk-sns/message_verifier.rb, line 142
def verify_hosted_by_aws!(uri)
  unless AWS_HOSTNAMES.any? { |pattern| pattern.match(uri.host) }
    msg = "signing cert is not hosted by AWS: #{uri}"
    raise VerificationError, msg
  end
end
verify_https!(uri) click to toggle source
# File lib/aws-sdk-sns/message_verifier.rb, line 135
def verify_https!(uri)
  unless uri.scheme == 'https'
    msg = "the SigningCertURL must be https, got: #{uri}"
    raise VerificationError, msg
  end
end
verify_pem!(uri) click to toggle source
# File lib/aws-sdk-sns/message_verifier.rb, line 149
def verify_pem!(uri)
  unless File.extname(uri.path) == '.pem'
    msg = "the SigningCertURL must link to a .pem file"
    raise VerificationError, msg
  end
end
verify_uri!(uri) click to toggle source
# File lib/aws-sdk-sns/message_verifier.rb, line 129
def verify_uri!(uri)
  verify_https!(uri)
  verify_hosted_by_aws!(uri)
  verify_pem!(uri)
end