How to authenticate the GKLocalPlayer on my 'third party server'?

后端 未结 10 415
鱼传尺愫
鱼传尺愫 2020-11-30 02:53

iOS7 introduced new GKLocalPlayer method generateIdentityVerificationSignatureWithCompletionHandler().

Does anyone know how to use it for good? I assum

相关标签:
10条回答
  • 2020-11-30 03:04

    Here is a C# WebApi server side version:

    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)
        {
            try
            {
                var cert = GetCertificate(auth.PublicKeyUrl);
                if (cert.Verify())
                {
                    var csp = cert.PublicKey.Key as RSACryptoServiceProvider;
                    if (csp != null)
                    {
                        var sha256 = new SHA256Managed();
                        var sig = ConcatSignature(auth.PlayerId, auth.BundleId, auth.Timestamp, auth.Salt);
                        var hash = sha256.ComputeHash(sig);
    
                        if (csp.VerifyHash(hash, CryptoConfig.MapNameToOID("SHA256"), Convert.FromBase64String(auth.Signature)))
                        {
                            // Valid user.
                            // Do server related user management stuff.
                            return true;
                        }
                    }
                }
    
                // Failure
                token = null;
                return false;
            }
            catch (Exception ex)
            {
                // Log the error
                token = null;
                return false;
            }
        }
    
        private static byte[] ToBigEndian(ulong value)
        {
            var buffer = new byte[8];
            for (int i = 0; i < 8; i++)
            {
                buffer[7 - i] = unchecked((byte)(value & 0xff));
                value = value >> 8;
            }
            return buffer;
        }
    
        private X509Certificate2 GetCertificate(string url)
        {
            var client = new WebClient();
            var rawData = client.DownloadData(url);
            return new X509Certificate2(rawData);
        }
    
        private byte[] ConcatSignature(string playerId, string bundleId, ulong timestamp, string salt)
        {
            var data = new List<byte>();
            data.AddRange(Encoding.UTF8.GetBytes(playerId));
            data.AddRange(Encoding.UTF8.GetBytes(bundleId));
            data.AddRange(ToBigEndian(timestamp));
            data.AddRange(Convert.FromBase64String(salt));
            return data.ToArray();
        }
    }
    
    
    public class GameCenterAuth
    {
        public string PlayerId { get; set; }
        public string BundleId { get; set; }
        public string Name { get; set; }
        public string PublicKeyUrl { get; set; }
        public string Signature { get; set; }
        public string Salt { get; set; }
        public ulong Timestamp { get; set; }
    }
    
    0 讨论(0)
  • 2020-11-30 03:14
    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
    

    Here's my ruby implementation (as module). Thanks to your Objective-C, it has been a lot easier.

    Please note, I had been forced to download the CA certificate on a third party ssl service website, because the public key certificate hasn't been signed Apple and Apple doesn't provide any CA certificate to validate sandbox game center certificate so far.

    I haven't tested this in production but it works fine in sandbox mode.

    0 讨论(0)
  • 2020-11-30 03:18

    Thanks to those who provided solutions in other languages.

    Here are the relevant bits of solution in Scala (trivial to convert to 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)
    

    where r is an instance of:

    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
    
    0 讨论(0)
  • 2020-11-30 03:20

    Thanks, @odyth. Thanks, @Lionel. I want to add Python version (based on yours) here. It has minor flaw - Apple certificate is not verified - there is no such API at pyOpenSSL binding.

    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):
        apple_cert = urllib2.urlopen(gc_public_key_url).read()
        gc_pkey_certificate = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_ASN1, apple_cert)
    
        payload = gc_player_id.encode('UTF-8') + app_bundle_id.encode('UTF-8') + struct.pack('>Q', int(gc_timestamp)) + gc_salt
    
        try:
            OpenSSL.crypto.verify(gc_pkey_certificate, gc_unverified_signature, payload, 'sha1')
            print 'Signature verification is done. Success!'
        except Exception as res:
            print res
    
    public_key_url = 'https://sandbox.gc.apple.com/public-key/gc-sb.cer'
    player_GC_ID = 'G:1870391344'
    timestamp = '1382621610281'
    your_app_bundle_id = 'com.myapp.bundle_id'
    
    with open('./salt.dat', 'rb') as f_salt:
        with open('./signature.dat', 'rb') as f_sign:
            authenticate_game_center_user(public_key_url, your_app_bundle_id, player_GC_ID, timestamp, f_salt.read(), f_sign.read())
    
    0 讨论(0)
  • 2020-11-30 03:22

    Here is my implementation in 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
    
    0 讨论(0)
  • 2020-11-30 03:25

    Here is how you can authenticate using objective c. If you need it in another language should be trivial to translate.

    -(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)
                    {
                        return; //some sort of error, can't authenticate right now
                    }
    
                    [self verifyPlayer:localPlayer.playerID publicKeyUrl:publicKeyUrl signature:signature salt:salt timestamp:timestamp];
    
    
                }];
            }
            else
            {
                NSLog(@"game center disabled");
            }
        };
    }
    
    -(void)verifyPlayer:(NSString *)playerID publicKeyUrl:(NSURL *)publicKeyUrl signature:(NSData *)signature salt:(NSData *)salt timestamp:(uint64_t)timestamp
    {
        //get certificate
        NSData *certificateData = [NSData dataWithContentsOfURL:publicKeyUrl];
    
        //build payload
        NSMutableData *payload = [[NSMutableData alloc] init];
        [payload appendData:[playerID dataUsingEncoding:NSASCIIStringEncoding]];
        [payload appendData:[[[NSBundle mainBundle] bundleIdentifier] dataUsingEncoding:NSASCIIStringEncoding]];
    
        uint64_t timestampBE = CFSwapInt64HostToBig(timestamp);
        [payload appendBytes:&timestampBE length:sizeof(timestampBE)];
        [payload appendData:salt];
    
        //sign
        SecCertificateRef certificateFromFile = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certificateData); // load the certificate
        SecPolicyRef secPolicy = SecPolicyCreateBasicX509();
    
        SecTrustRef trust;
        OSStatus statusTrust = SecTrustCreateWithCertificates( certificateFromFile, secPolicy, &trust);
        if(statusTrust != errSecSuccess)
        {
            NSLog(@"could not create trust");
            return;
        }
    
        SecTrustResultType resultType;
        OSStatus statusTrustEval =  SecTrustEvaluate(trust, &resultType);
        if(statusTrustEval != errSecSuccess)
        {
            NSLog(@"could not evaluate trust");
            return;
        }
    
        if(resultType != kSecTrustResultProceed && resultType != kSecTrustResultRecoverableTrustFailure)
        {
            NSLog(@"server can not be trusted");
            return;
        }
    
        SecKeyRef publicKey = SecTrustCopyPublicKey(trust);
        uint8_t sha256HashDigest[CC_SHA256_DIGEST_LENGTH];
        CC_SHA256([payload bytes], (CC_LONG)[payload length], sha256HashDigest);
    
        //check to see if its a match
        OSStatus verficationResult = SecKeyRawVerify(publicKey,  kSecPaddingPKCS1SHA256, sha256HashDigest, CC_SHA256_DIGEST_LENGTH, (const uint8_t *)[signature bytes], [signature length]);
    
        CFRelease(publicKey);
        CFRelease(trust);
        CFRelease(secPolicy);
        CFRelease(certificateFromFile);
        if (verficationResult == errSecSuccess)
        {
            NSLog(@"Verified");
        }
        else
        {
            NSLog(@"Danger!!!");
        }
    }
    

    EDIT:

    as of March 2nd 2015, apple now uses SHA256 instead of SHA1 on the certificate. https://devforums.apple.com/thread/263789?tstart=0

    0 讨论(0)
提交回复
热议问题