21

iOS7 では、新しい GKLocalPlayer メソッドが導入されましgenerateIdentityVerificationSignatureWithCompletionHandler()た。

誰かがそれをうまく使う方法を知っていますか? Appleサーバー側にパブリックAPIがあると思います..

4

10 に答える 10

4

これを PHP に実装するには、かなりの時間がかかりました。それでは、私の結果を共有したいと思います。

ドキュメンテーション

Apple で非常に簡単なドキュメントを見つけることができます

[...]

  1. サード パーティ サーバーの publicKeyURL を使用して、公開キーをダウンロードします。
  2. 公開鍵が Apple によって署名されていることを適切な署名機関に確認します。
  3. プレーヤーの playerID と bundleID を取得します。
  4. 次の情報を、リストされている順序でデータ バッファーに連結します。
    • UTF-8 形式の playerID パラメータ
    • UTF-8 形式の bundleID パラメータ
    • ビッグエンディアン UInt-64 形式のタイムスタンプ パラメータ
    • ソルト パラメータ
  5. バッファーの SHA-256 ハッシュ値を生成します。
  6. ステップ 3 でダウンロードした公開鍵を使用して、ステップ 7 で生成されたハッシュ値が API によって提供された署名パラメーターと一致することを確認します。

知らせ!7 番は PHP の罠で、何時間もかかりました。未加工の連結文字列のみをopenssl_verify()関数に渡す必要があります。

PHP を使用して「サードパーティ サーバー」で GKLocalPlayer を認証するにはどうすればよいですか?という質問の2014 年 7 月 9 日からの更新 問題を見つけるのに役立ちました。

最終ソース

<?php
// signature, publicKeyUrl, timestamp and salt are included in the base64/json data you will receive by calling generateIdentityVerificationSignatureWithCompletionHandler.

$timestamp = $params["timestamp"]; // e.g. 1447754520194
$user_id = $params["user_id"]; // e.g. G:20010412315
$bundle_id = "com.example.test";
$public_key_url = $params["publicKeyUrl"]; // e.g. https://static.gc.apple.com/public-key/gc-prod-2.cer
$salt = base64_decode($params["salt"]); // Binary
$signature = base64_decode($params["signature"]); // Binary

// Timestamp is unsigned 64-bit integer big endian
$highMap = 0xffffffff00000000;
$lowMap = 0x00000000ffffffff;
$higher = ($timestamp & $highMap) >>32;
$lower = $timestamp & $lowMap;
$timestamp = pack('NN', $higher, $lower);

// Concatenate the string
$data = $user_id . $bundle_id . $timestamp . $salt;

// ATTENTION!!! Do not hash it! $data = hash("sha256", $packed);

// Fetch the certificate. This is dirty because it is neither cached nor verified that the url belongs to Apple.
$ssl_certificate = file_get_contents($public_key_url);

$pem = chunk_split(base64_encode($ssl_certificate), 64, "\n");
$pem = "-----BEGIN CERTIFICATE-----\n" . $pem . "-----END CERTIFICATE-----\n";

// it is also possible to pass the $pem string directly to openssl_verify
if (($pubkey_id = openssl_pkey_get_public($pem)) === false) {
    echo "invalid public key\n";
    exit;
}

// Verify that the signature is correct for $data
$verify_result = openssl_verify($data, $signature, $pubkey_id, OPENSSL_ALGO_SHA256);

openssl_free_key($pubkey_id);

switch($verify_result) {
  case 1:
    echo "Signature is ok.\n";
    break;
  case 0:
    echo "Signature is wrong.\n";
    break;
  default:
    echo "An error occurred.\n";
    break;
}
于 2015-11-17T15:26:02.890 に答える
0

これが私の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
于 2016-06-10T10:39:42.263 に答える
0

これは、更新および改善された Ruby バージョンです。Apple サンドボックスでテストしましたが、本番環境ではまだテストしていません。公開鍵の URL から受け取った証明書を検証するために、CA 証明書を取得する場所も文書化しました。

# 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
于 2015-06-24T22:23:32.957 に答える