How to authenticate the GKLocalPlayer on my ‘third party server’?
iOS7引入了新的GKLocalPlayer方法
有人知道如何永久使用它吗?
我假设苹果服务器端会有一些公共 API ..
这是一个 C# WebApi 服务器端版本:
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 |
public class GameCenterController : ApiController { // POST api/gamecenter public HttpResponseMessage Post(GameCenterAuth data) { string token; if (ValidateSignature(data, out token)) { return Request.CreateResponse(HttpStatusCode.OK, token); } return Request.CreateErrorResponse(HttpStatusCode.Forbidden, string.Empty); } private bool ValidateSignature(GameCenterAuth auth, out string token) if (csp.VerifyHash(hash, CryptoConfig.MapNameToOID("SHA256"), Convert.FromBase64String(auth.Signature))) // Failure private static byte[] ToBigEndian(ulong value) private X509Certificate2 GetCertificate(string url) private byte[] ConcatSignature(string playerId, string bundleId, ulong timestamp, string salt) public class GameCenterAuth |
以下是使用objective C 进行身份验证的方法。如果你需要它在另一种语言应该是微不足道的翻译。
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 |
-(void)authenticate { __weak GKLocalPlayer *localPlayer = [GKLocalPlayer localPlayer]; localPlayer.authenticateHandler = ^(UIViewController *viewController, NSError *error) { if(viewController) { [[[UIApplication sharedApplication] keyWindow].rootViewController presentViewController:viewController animated:YES completion:nil]; } else if(localPlayer.isAuthenticated == YES) { [localPlayer generateIdentityVerificationSignatureWithCompletionHandler:^(NSURL *publicKeyUrl, NSData *signature, NSData *salt, uint64_t timestamp, NSError *error) { if(error != nil) [self verifyPlayer:localPlayer.playerID publicKeyUrl:publicKeyUrl signature:signature salt:salt timestamp:timestamp]; }]; -(void)verifyPlayer:(NSString *)playerID publicKeyUrl:(NSURL *)publicKeyUrl signature:(NSData *)signature salt:(NSData *)salt timestamp:(uint64_t)timestamp //build payload uint64_t timestampBE = CFSwapInt64HostToBig(timestamp); //sign SecTrustRef trust; SecTrustResultType resultType; if(resultType != kSecTrustResultProceed && resultType != kSecTrustResultRecoverableTrustFailure) SecKeyRef publicKey = SecTrustCopyPublicKey(trust); //check to see if its a match CFRelease(publicKey); |
编辑:
自 2015 年 3 月 2 日起,Apple 现在在证书上使用 SHA256 而不是 SHA1。 https://devforums.apple.com/thread/263789?tstart=0
我花了很多时间在 PHP 中实现它。现在我想分享我的结果。
文档
您可以在 Apple 找到一个非常简单的文档:https://developer.apple.com/library/ios/documentation/GameKit/Reference/GKLocalPlayer_Ref/index.html#//apple_ref/occ/instm/GKLocalPlayer/generateIdentityVerificationSignatureWithCompletionHandler
[…]
- UTF-8 格式的 playerID 参数
- UTF-8 格式的 bundleID 参数
- Big-Endian UInt-64 格式的时间戳参数
- salt参数
注意!数字 7 是 PHP 中的一个陷阱,花了我几个小时。您只需将原始连接字符串传递给 openssl_verify() 函数。
2014 年 7 月 9 日更新的问题如何使用 PHP 在我的”第三方服务器”上验证 GKLocalPlayer?帮我找到了问题。
最终来源
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
<?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 // Timestamp is unsigned 64-bit integer big endian // Concatenate the string // 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. $pem = chunk_split(base64_encode($ssl_certificate), 64,"/ // it is also possible to pass the $pem string directly to openssl_verify // Verify that the signature is correct for $data openssl_free_key($pubkey_id); switch($verify_result) { |
添加 Python 的答案,但使用 PyCrypto 2.6(这是 Google App Engine 解决方案)。
另请注意,此处未完成下载后对公共证书的验证,类似于上面使用 OpenSSL 的 python 答案。这一步真的有必要吗?如果我们检查公钥 URL 是否要访问苹果域并且它使用 ssl (https),??这是否意味着它受到保护免受中间人攻击?
不管怎样,这里是代码。请注意,二进制文本在连接和使用之前会重新转换为二进制。此外,我必须更新我的本地 python 安装以使用 PyCrypto 2.6,然后才能使用:
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
from Crypto.PublicKey import RSA from Crypto.Signature import PKCS1_v1_5 from Crypto.Hash import SHA from base64 import b64decode from Crypto.Util.asn1 import DerSequence from binascii import a2b_base64 import struct import urlparse def authenticate_game_center_user(gc_public_key_url, app_bundle_id, gc_player_id, gc_timestamp, gc_salt, gc_unverified_signature): apple_cert = urllib2.urlopen(gc_public_key_url).read() #Verify the url is https and is pointing to an apple domain. cert = DerSequence() rsakey = RSA.importKey(subjectPublicKeyInfo) payload = gc_player_id.encode(‘UTF-8’) if verifier.verify(digest, b64decode(gc_unverified_signature)): |
谢谢,@odyth。谢谢,@Lionel。
我想在这里添加 Python 版本(基于你的)。它有一个小缺陷 – Apple 证书未经验证 – 在 pyOpenSSL 绑定中没有这样的 API。
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
import urllib2 import OpenSSL import struct def authenticate_game_center_user(gc_public_key_url, app_bundle_id, gc_player_id, gc_timestamp, gc_salt, gc_unverified_signature): payload = gc_player_id.encode(‘UTF-8’) + app_bundle_id.encode(‘UTF-8’) + struct.pack(‘>Q’, int(gc_timestamp)) + gc_salt try: public_key_url = ‘https://sandbox.gc.apple.com/public-key/gc-sb.cer’ with open(‘./salt.dat’, ‘rb’) as f_salt: |
感谢代码示例,golang 解决方案来了:
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
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) { payload := new(bytes.Buffer) return verifyRsa(cert, sig, payload.Bytes()) func verifyRsa(key, sig, content []byte) error { h := sha256.New() err = rsa.VerifyPKCS1v15(pub, crypto.SHA256, digest, sig) |
一个小小的http助手
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
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 { |
测试代码
1
2 3 4 5 6 7 8 9 10 11 12 13 |
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) } |
验证证书下载 url 的小功能。防止从任何地方下载任何东西
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
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") { if path.Ext(fullUrl) !=".cer" { |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 | require ‘base64’
require ‘httparty’ module GameCenter # HHTTParty settings def authenticate_game_center_user(gc_public_key_url, gc_player_id, gc_timestamp, gc_salt, gc_unverified_signature) # Check public key certificate # Test signature # Return player ID def build_payload(player_id, timestamp, salt) private def get_gc_public_key_certificate(url) def get_ca_certificate def public_key_certificate_is_valid?(pkey_cert) def signature_is_valid?(pkey_cert, signature, payload) end |
这是我的 ruby?? 实现(作为模块)。多亏了您的 Objective-C,它变得容易多了。
请注意,我在第三方 ssl 服务网站上被迫下载 CA 证书,因为公钥证书尚未签署 Apple 并且 Apple 未提供任何 CA 证书来验证沙盒游戏中心到目前为止的证书。
我还没有在生产中测试过这个,但是在沙盒模式下它可以正常工作。
这是我在 Elixir 中的实现。
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
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 |
这是一个更新和改进的 Ruby 版本。我已经使用 Apple 沙箱对其进行了测试,但还没有进行生产。我还记录了从哪里获取 CA 证书,以验证您从公钥 URL 收到的证书。
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 |
# 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 private def self.get_public_key_certificate(url) def self.public_key_url_is_valid?(public_key_url) def self.public_key_certificate_is_valid?(pkey_cert) def self.signature_is_valid?(pkey_cert, signature, payload) def self.get_ca_certificate def self.ca_certificate_text |
感谢那些提供其他语言解决方案的人。
以下是 Scala 中的相关解决方案(转换为 Java 很简单):
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
private def verify(
signatureAlgorithm: String, publicKey: PublicKey, message: Array[Byte], signature: Array[Byte]): Boolean = { val sha1Signature = Signature.getInstance(signatureAlgorithm) val x509Cert = Try(certificateFactory.generateCertificate(new ByteArrayInputStream(publicKeyBytes)).asInstanceOf[X509Certificate]) val buffer = val result = verify(signatureAlgorithm.getOrElse("SHA256withRSA"), pk, buffer, Base64.decode(r.signature)) log.info("verification result {} for request {}", result, r) |
其中 r 是一个实例:
1
2 3 4 5 6 7 |
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 |
原创文章,作者:ItWorker,如若转载,请注明出处:https://blog.ytso.com/268685.html