20 votes

Comment authentifier le GKLocalPlayer sur mon 'serveur tiers' ?

IOS7 a introduit une nouvelle méthode GKLocalPlayer generateIdentityVerificationSignatureWithCompletionHandler() .

Quelqu'un sait-il comment l'utiliser à bon escient ? Je suppose qu'il y aura une API publique du côté du serveur d'Apple

2voto

Lionel Points 21
require 'base64'
require 'httparty'

module GameCenter
  include HTTParty

  # HHTTParty settings
  HTTPARTY_TIMEOUT = 10

  def authenticate_game_center_user(gc_public_key_url, gc_player_id, gc_timestamp, gc_salt, gc_unverified_signature)
    # Get game center public key certificate
    gc_pkey_certificate = get_gc_public_key_certificate(gc_public_key_url)

    # Check public key certificate
    unless public_key_certificate_is_valid?(gc_pkey_certificate) do
        # Handle error
    end
    # Check SSL errors
    unless OpenSSL.errors.empty? do
        # Handle OpenSSL errors
    end
    # Payload building
    payload = build_payload(gc_player_id, gc_timestamp, gc_salt)

    # Test signature
    unless signature_is_valid?(gc_pkey_certificate, gc_unverified_signature, payload) do
        # Handle error
    end
    # Check SSL errors
    unless OpenSSL.errors.empty? do
        # Handle OpenSSL errors
    end

    # Return player ID
    gc_player_id
  end

  def build_payload(player_id, timestamp, salt)
    player_id.encode("UTF-8") + "com.myapp.bundle_id".encode("UTF-8") + [timestamp.to_i].pack("Q>") + salt
  end

  private

  def get_gc_public_key_certificate(url)
    cert = HTTParty.get(url, timeout: HTTPARTY_TIMEOUT, debug_output: Rails.env.production?)
    OpenSSL::X509::Certificate.new(cert)
  rescue SocketError => e
    puts "Key error: " + e.inspect.to_s
  end

  def get_ca_certificate
    OpenSSL::X509::Certificate.new(File.read('./certs/apple/verisign_class_3_code_signing_2010_ca.cer'))
  end

  def public_key_certificate_is_valid?(pkey_cert)
    pkey_cert.verify(get_ca_certificate.public_key)
  end

  def signature_is_valid?(pkey_cert, signature, payload)
    pkey_cert.public_key.verify(OpenSSL::Digest::SHA1.new, signature, payload)
  end

end

Voici mon implémentation ruby (en tant que module). Grâce à votre Objective-C, cela a été beaucoup plus facile.

Veuillez noter que j'ai été obligé de télécharger le certificat CA sur un site web de service ssl tiers, car le certificat de clé publique n'a pas été signé par Apple et Apple ne fournit aucun certificat CA pour valider le certificat de sandbox game center jusqu'à présent.

Je n'ai pas testé cela en production mais cela fonctionne bien en mode sandbox.

2voto

davyzhang Points 345

Merci pour les exemples de code, voici la solution golang :

func DownloadCert(url string) []byte {
    b, err := inet.HTTPGet(url)
    if err != nil {
        log.Printf("http request error %s", err)
        return nil
    }
    return b
}

func VerifySig(sSig, sGcId, sBundleId, sSalt, sTimeStamp string, cert []byte) (err error) {
    sig, err := base64.StdEncoding.DecodeString(sSig)
    if err != nil {
        return
    }
    salt, err := base64.StdEncoding.DecodeString(sSalt)
    if err != nil {
        return
    }
    timeStamp, err := strconv.ParseUint(sTimeStamp, 10, 64)
    if err != nil {
        return
    }

    payload := new(bytes.Buffer)
    payload.WriteString(sGcId)
    payload.WriteString(sBundleId)
    binary.Write(payload, binary.BigEndian, timeStamp)
    payload.Write(salt)

    return verifyRsa(cert, sig, payload.Bytes())
}

func verifyRsa(key, sig, content []byte) error {
    cert, err := x509.ParseCertificate(key)
    if err != nil {
        log.Printf("parse cert error %s", err)
        return err
    }
    pub := cert.PublicKey.(*rsa.PublicKey)

    h := sha256.New()
    h.Write(content)
    digest := h.Sum(nil)

    err = rsa.VerifyPKCS1v15(pub, crypto.SHA256, digest, sig)
    return err
}

une petite aide http

func HTTPGet(fullUrl string) (content []byte, err error) {
    log.Printf("http get url %s", fullUrl)
    resp, err := http.Get(fullUrl)
    if err != nil {
        log.Printf("url can not be reached %s,%s", fullUrl, err)
        return
    }

    if resp.StatusCode != http.StatusOK {
        return nil, errors.New("ERROR_STATUS_NOT_OK")
    }
    body := resp.Body
    content, err = ioutil.ReadAll(body)
    if err != nil {
        log.Printf("url read error %s, %s", fullUrl, err)
        return
    }
    body.Close()
    return
}

code de test

func TestVerifyFull(t *testing.T) {
    cert := DownloadCert("https://sandbox.gc.apple.com/public-key/gc-sb-2.cer")
    if cert == nil {
        log.Printf("cert download error ")
    }
    sig := "sig as base64"
    salt := "salt as base64"
    timeStamp := "1442816155502"
    gcId := "G:12345678"
    bId := "com.xxxx.xxxx"
    err := VerifySig(sig, gcId, bId, salt, timeStamp, cert)
    log.Printf("result %v", err)
}

une petite fonction pour valider l'url de téléchargement du certificat. Empêcher de télécharger de n'importe où n'importe quoi

func IsValidCertUrl(fullUrl string) bool {
    //https://sandbox.gc.apple.com/public-key/gc-sb-2.cer
    uri, err := url.Parse(fullUrl)
    if err != nil {
        log.Printf("not a valid url %s", fullUrl)
        return false
    }

    if !strings.HasSuffix(uri.Host, "apple.com") {
        log.Printf("not a valid host %s", fullUrl)
        return false
    }

    if path.Ext(fullUrl) != ".cer" {
        log.Printf("not a valid ext %s, %s", fullUrl, path.Ext(fullUrl))
        return false
    }
    return true
}

0voto

Peter vR Points 111

Merci à ceux qui ont fourni des solutions dans d'autres langues.

Voici les éléments pertinents de la solution en Scala (facile à convertir en Java) :

private def verify(
  signatureAlgorithm: String, 
  publicKey: PublicKey, 
  message: Array[Byte], 
  signature: Array[Byte]): Boolean = {

  val sha1Signature = Signature.getInstance(signatureAlgorithm)
  sha1Signature.initVerify(publicKey)
  sha1Signature.update(message)
  sha1Signature.verify(signature)
}

val x509Cert = Try(certificateFactory.generateCertificate(new ByteArrayInputStream(publicKeyBytes)).asInstanceOf[X509Certificate])
x509Cert.foreach { cert =>
  signatureAlgorithm = Some(cert.getSigAlgName)
}
x509Cert.map(_.getPublicKey) match {
  case Success(pk) =>
    log.debug("downloaded public key successfully")
    publicKey = Some(pk)
}

val buffer = 
  r.id.getBytes("UTF-8") ++ 
  bundleId.getBytes("UTF-8") ++
  ByteBuffer.allocate(8).putLong(r.timestamp).array() ++ 
  Base64.decode(r.salt)

val result = verify(signatureAlgorithm.getOrElse("SHA256withRSA"), pk, buffer, Base64.decode(r.signature))

log.info("verification result {} for request {}", result, r)

où r est une instance de :

case class IOSIdentityVerificationRequest(
  id: PlayerIdentity, // String
  publicKeyURL: String, 
  signature: String, // base64 encoded bytes
  salt: String, // base64 encoded bytes
  timestamp: Long, 
  error: Option[String]) extends IdentityVerificationRequest

0voto

Chad Remesch Points 1

Voici une version Ruby mise à jour et améliorée. Je l'ai testée avec le bac à sable d'Apple, mais pas encore avec la production. J'ai également documenté où obtenir le certificat CA afin de vérifier le certificat que vous recevez de l'URL de la clé publique.

# iOS Game Center verifier for 3rd party game servers written in Ruby.
#
# *** Credits ***
#   Based off of code and comments at https://stackoverflow.com/questions/17408729/how-to-authenticate-the-gklocalplayer-on-my-third-party-server
#
# *** Improvements ***
#   This version uses Ruby's built in HTTP client instead of a 3rd party gem.
#   It's updated to use SHA256 instead of SHA1.
#   It Base64 decodes the salt and signature.  If your client or server already does this then you will need to remove the calls to Base64.decode64().
#   It validates that the public key URL is from apple.com.
#   It has been tested with Apple's Game Center's sandbox public key URL (https://sandbox.gc.apple.com/public-key/gc-sb-2.cer) and works as of June 24th, 2015.
#
# *** Notes on public key certificate validation ***
#   You will need the correct code signing CA to verify the certificate returned from the pubic key URL.
#   You can download/verify the CA certificate here: https://knowledge.symantec.com/support/code-signing-support/index?page=content&actp=CROSSLINK&id=AR2170
#   I have embedded the CA certificate for convenience so that you don't need to save it to your filesystem.
#   When the public key URL changes in the future, you may need to update the text in the ca_certificate_text() method.
#
# *** Usage ***
#   verified, reason = GameCenterVerifier.verify(...)

class GameCenterVerifier
  # Verify that user provided Game Center data is valid.
  # False will be returned along with a reason if any validations fail.
  # Otherwise, it will return true and a nil reason if all validations pass.
  def self.verify(game_center_id, public_key_url, timestamp, salt, signature, bundle_id)
    salt = Base64.decode64(salt)
    signature = Base64.decode64(signature)
    payload = game_center_id.encode('UTF-8') + bundle_id.encode('UTF-8') + [timestamp.to_i].pack('Q>') + salt
    pkey_certificate = get_public_key_certificate(public_key_url)
    return false, 'Invalid public key url' unless public_key_url_is_valid?(public_key_url)
    return false, 'Invalid public key certificate' unless public_key_certificate_is_valid?(pkey_certificate)
    return false, 'OpenSSL errors (before signature check)' unless OpenSSL.errors.empty?
    return false, 'Invalid signature' unless signature_is_valid?(pkey_certificate, signature, payload)
    return false, 'OpenSSL errors (after signature check)' unless OpenSSL.errors.empty?
    return true, nil
  end

  private

  def self.get_public_key_certificate(url)
    uri = URI.parse(url)
    http = Net::HTTP.new(uri.host, uri.port)
    request = Net::HTTP::Get.new(uri.request_uri)
    http.use_ssl = true
    http.open_timeout = 5
    http.read_timeout = 5
    cert = http.request(request).body
    OpenSSL::X509::Certificate.new(cert)
  end

  def self.public_key_url_is_valid?(public_key_url)
    uri = URI(public_key_url)
    tokens = uri.host.split('.')
    return false if uri.scheme != 'https'
    return false if tokens[-1] != 'com' || tokens[-2] != 'apple'
    true
  end

  def self.public_key_certificate_is_valid?(pkey_cert)
    pkey_cert.verify(get_ca_certificate.public_key)
  end

  def self.signature_is_valid?(pkey_cert, signature, payload)
    pkey_cert.public_key.verify(OpenSSL::Digest::SHA256.new, signature, payload)
  end

  def self.get_ca_certificate
    OpenSSL::X509::Certificate.new(ca_certificate_text)
  end

  def self.ca_certificate_text
    data = <<EOF
-----BEGIN CERTIFICATE-----
MIIFWTCCBEGgAwIBAgIQPXjX+XZJYLJhffTwHsqGKjANBgkqhkiG9w0BAQsFADCB
yjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQL
ExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJp
U2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxW
ZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0
aG9yaXR5IC0gRzUwHhcNMTMxMjEwMDAwMDAwWhcNMjMxMjA5MjM1OTU5WjB/MQsw
CQYDVQQGEwJVUzEdMBsGA1UEChMUU3ltYW50ZWMgQ29ycG9yYXRpb24xHzAdBgNV
BAsTFlN5bWFudGVjIFRydXN0IE5ldHdvcmsxMDAuBgNVBAMTJ1N5bWFudGVjIENs
YXNzIDMgU0hBMjU2IENvZGUgU2lnbmluZyBDQTCCASIwDQYJKoZIhvcNAQEBBQAD
ggEPADCCAQoCggEBAJeDHgAWryyx0gjE12iTUWAecfbiR7TbWE0jYmq0v1obUfej
DRh3aLvYNqsvIVDanvPnXydOC8KXyAlwk6naXA1OpA2RoLTsFM6RclQuzqPbROlS
Gz9BPMpK5KrA6DmrU8wh0MzPf5vmwsxYaoIV7j02zxzFlwckjvF7vjEtPW7ctZlC
n0thlV8ccO4XfduL5WGJeMdoG68ReBqYrsRVR1PZszLWoQ5GQMWXkorRU6eZW4U1
V9Pqk2JhIArHMHckEU1ig7a6e2iCMe5lyt/51Y2yNdyMK29qclxghJzyDJRewFZS
AEjM0/ilfd4v1xPkOKiE1Ua4E4bCG53qWjjdm9sCAwEAAaOCAYMwggF/MC8GCCsG
AQUFBwEBBCMwITAfBggrBgEFBQcwAYYTaHR0cDovL3MyLnN5bWNiLmNvbTASBgNV
HRMBAf8ECDAGAQH/AgEAMGwGA1UdIARlMGMwYQYLYIZIAYb4RQEHFwMwUjAmBggr
BgEFBQcCARYaaHR0cDovL3d3dy5zeW1hdXRoLmNvbS9jcHMwKAYIKwYBBQUHAgIw
HBoaaHR0cDovL3d3dy5zeW1hdXRoLmNvbS9ycGEwMAYDVR0fBCkwJzAloCOgIYYf
aHR0cDovL3MxLnN5bWNiLmNvbS9wY2EzLWc1LmNybDAdBgNVHSUEFjAUBggrBgEF
BQcDAgYIKwYBBQUHAwMwDgYDVR0PAQH/BAQDAgEGMCkGA1UdEQQiMCCkHjAcMRow
GAYDVQQDExFTeW1hbnRlY1BLSS0xLTU2NzAdBgNVHQ4EFgQUljtT8Hkzl699g+8u
K8zKt4YecmYwHwYDVR0jBBgwFoAUf9Nlp8Ld7LvwMAnzQzn6Aq8zMTMwDQYJKoZI
hvcNAQELBQADggEBABOFGh5pqTf3oL2kr34dYVP+nYxeDKZ1HngXI9397BoDVTn7
cZXHZVqnjjDSRFph23Bv2iEFwi5zuknx0ZP+XcnNXgPgiZ4/dB7X9ziLqdbPuzUv
M1ioklbRyE07guZ5hBb8KLCxR/Mdoj7uh9mmf6RWpT+thC4p3ny8qKqjPQQB6rqT
og5QIikXTIfkOhFf1qQliZsFay+0yQFMJ3sLrBkFIqBgFT/ayftNTI/7cmd3/SeU
x7o1DohJ/o39KK9KEr0Ns5cF3kQMFfo2KwPcwVAB8aERXRTl4r0nS1S+K4ReD6bD
dAUK75fDiSKxH3fzvc1D1PFMqT+1i4SvZPLQFCE=
-----END CERTIFICATE-----
EOF
  end
end

0voto

Jake Song Points 1

Voici mon implémentation en Elixir.

def verify_login(player_id, public_key_url, timestamp, salt64, signature64, bundle_id) do
  salt = Base.decode64!(salt64)
  pay_load = <<player_id :: binary, bundle_id :: binary, timestamp :: big-size(64), salt :: binary>>
  pkey_cert = get_public_key_certificate(public_key_url)
  cert = :public_key.pkix_decode_cert(pkey_cert, :otp)
  case cert do
    {:OTPCertificate,
     {:OTPTBSCertificate, _, _, _, _, _, _,
      {:OTPSubjectPublicKeyInfo, _, key}, _, _, _}, _, _} ->
      signature = Base.decode64!(signature64)
      case :public_key.verify(pay_load, :sha256, signature, key) do
        true ->
          :ok
        false ->
          {:error, "apple login verify failed"}
      end
  end
end

def get_public_key_certificate(url) do
  case HTTPoison.get(url) do
    {:ok, %HTTPoison.Response{body: body}} ->
      body
  end
end

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X