class Gamecenter::Auth

Constants

VERSION

Public Instance Methods

get_public_key_certificate(public_key_url) click to toggle source

Get a public key certificate for given URL. Caches the results, depending on the configuration. @param [String] public_key_url the URL of the certificate to fetch @return [OpenSSL::X509::Certificate] certificate found at given URL or nil if there was an error

# File lib/gamecenter/auth.rb, line 148
def get_public_key_certificate(public_key_url)
  if @@cache_public_keys
    # caching is enabled
    cache = (@@_public_key_cache ||= {})
    cert = cache[public_key_url]
    unless cert && cert.not_after > Time.now
      # no cache hit or certificate expired
      cache.delete public_key_url

      cert = request_public_key_certificate public_key_url

      available_entries = @@public_key_cache_entries - cache.size
      # check if there are free entries
      unless available_entries > 0
        # there are not, randomly delete enough to make room for this certificate
        cache.keys.sample(available_entries.abs + 1).each { |key|
          cache.delete key
        }
      end
      cache[public_key_url] = cert
    end

    cert
  else
    # caching is disabled
    request_public_key_certificate public_key_url
  end
end
request_public_key_certificate(public_key_url) click to toggle source

Fetch a certificate from given URL. @param [String] public_key_url the URL to fetch the certificate from @return [OpenSSL::X509::Certificate] certificate found at given URL or nil if there was an error

# File lib/gamecenter/auth.rb, line 180
def request_public_key_certificate(public_key_url)
  uri = URI.parse public_key_url
  begin
    Net::HTTP.start(uri.host, uri.port,
                    use_ssl: uri.scheme == 'https',
                    open_timeout: @@request_public_key_open_timeout,
                    read_timeout: @@request_public_key_read_timeout,
                    ssl_timeout: @@request_public_key_ssl_timeout) do |http|
      request = Net::HTTP::Get.new uri.request_uri
      response = http.request request # Net::HTTPResponse object
      begin
        return OpenSSL::X509::Certificate.new(response.body) if response.body
      rescue OpenSSL::X509::CertificateError => e
        logger.error e
      end
    end
  rescue Net::OpenTimeout, Net::ReadTimeout, OpenSSL::SSL::SSLError => e
    logger.error e
  end

  nil
end
verify_player(player_id, bundle_id, public_key_url, signature, salt, timestamp) click to toggle source

Verifies the identity of the given player. Takes all return values from GameKit's generateIdentityVerificationSignatureWithCompletionHandler method. @see developer.apple.com/library/prerelease/ios/documentation/GameKit/Reference/GKLocalPlayer_Ref/index.html#//apple_ref/occ/instm/GKLocalPlayer/generateIdentityVerificationSignatureWithCompletionHandler: @param [String] player_id the playerID from the player to verify identity of @param [String] bundle_id the app's bundleID @param [String] public_key_url the publicKeyURL property returned from the GameKit framework @param [String] signature the signature property returned from the GameKit framework @param [String] salt the salt property returned from the GameKit framework @param [Integer] timestamp the timestamp property returned from the GameKit framework @return [Boolean] true if player could be verified, false if not

# File lib/gamecenter/auth.rb, line 81
def verify_player(player_id, bundle_id, public_key_url, signature, salt, timestamp)
  unless verify_public_key_url public_key_url
    logger.debug { "public key url invalid: #{public_key_url}" }
    return false
  end

  cert = get_public_key_certificate public_key_url
  unless cert
    logger.debug { "could not get certificate from: #{public_key_url}" }
    return false
  end
  if @@verify_issuer_certificate && !verify_public_key_certificate(cert)
    logger.debug { "failed verification of public key certificate from: #{public_key_url}" }
    return false
  end

  salt_decoded = @@base64_decode_salt ? Base64.decode64(salt) : salt
  payload = "#{player_id}#{bundle_id}#{[timestamp.to_i].pack('Q>')}#{salt_decoded}"
  signature_decoded = @@base64_decode_signature ? Base64.decode64(signature) : signature
  unless verify_signature cert, signature_decoded, payload
    logger.debug { "failed signature validation for player id #{player_id}, bundle id #{bundle_id}, timestamp #{timestamp}, salt #{salt} (decode: #{@@base64_decode_salt}), signature #{signature} (decode: #{@@base64_decode_signature}) with certificate from: #{public_key_url}" }
    return false
  end

  true
end
verify_public_key_certificate(public_key_cert) click to toggle source

Checks if given public key certificate can be verified with the CA certificate. @param [OpenSSL::X509::Certificate] public_key_cert a previously fetched public key certificate object @return [Boolean] true if certificate could be verified against the CA certificate, false if it couldn't

# File lib/gamecenter/auth.rb, line 126
def verify_public_key_certificate(public_key_cert)
  verified = public_key_cert.verify(@@ca_certificate.public_key)
  no_errors = OpenSSL.errors.empty? # this method has to be called always as it empties the OpenSSL error stack

  verified && no_errors
end
verify_public_key_url(public_key_url) click to toggle source

Verifies that the public key url originates from one of Apple's secured servers. @param [String] public_key_url The publicKeyURL property returned from the GameKit framework @return [Boolean] true if url verification was successful, false if url fails verification

# File lib/gamecenter/auth.rb, line 111
def verify_public_key_url(public_key_url)
  url_ok = false
  begin
    uri = URI.parse public_key_url
    url_ok = uri.scheme == 'https' && !!(uri.host =~ /\.apple\.com$/i)
  rescue URI::InvalidURIError => e
    logger.error e
  end

  url_ok
end
verify_signature(public_key_cert, signature, payload) click to toggle source

Verifies the signature of given payload with given public key certificate. @param [OpenSSL:X509::Certificate] public_key_cert a previously fetched public key certificate object @param [String] signature the signature to be verified @param [String] payload the payload to verify the signature for @return [Boolean] true if signature is valid for given certificate and payload, false if it isn't

# File lib/gamecenter/auth.rb, line 138
def verify_signature(public_key_cert, signature, payload)
  verified = public_key_cert.public_key.verify(OpenSSL::Digest::SHA256.new, signature, payload)
  no_errors = OpenSSL.errors.empty? # this method has to be called always as it empties the OpenSSL error stack

  verified && no_errors
end

Private Instance Methods

logger() click to toggle source
# File lib/gamecenter/auth.rb, line 205
def logger
  @@logger ||= Logger.new(STDERR)
end