Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to authenticate GKLocalPlayer on my third-party-server using Java JDK 1.7?

Using the Apple's Game Center authentication verification steps outlined here, the verification logic below has been implemented using Java. However, this always fails.

import java.net.URL;

import java.nio.ByteBuffer;

import java.nio.ByteOrder;

import java.security.KeyPair;

import java.security.KeyPairGenerator;

import java.security.MessageDigest;

import java.security.PrivateKey;

import java.security.PublicKey;

import java.security.SecureRandom;

import java.security.Signature;

import java.security.cert.Certificate;

import java.security.cert.CertificateFactory;

import java.security.spec.AlgorithmParameterSpec;

import java.util.Arrays;

import javax.crypto.Cipher;

import javax.xml.bind.DatatypeConverter;

public class Verifier {

    public static void main(String[] args) {

        verify1();   

    }



    public static void verify1() {

        try {

            byte[] playerID = "G:90082947".getBytes("UTF-8");

            byte[] bundleID = "com.appledts.GameCenterSamples".getBytes("UTF-8");



            long ts = 1392078336714L;

            final ByteBuffer tsByteBuffer = ByteBuffer.allocate(8);

            tsByteBuffer.order(ByteOrder.BIG_ENDIAN);

            tsByteBuffer.putLong(ts);           

            byte[] timestamp = tsByteBuffer.array();



            byte[] salt = DatatypeConverter.parseBase64Binary("xmvbZQ==");



            byte[] sigToCheck = DatatypeConverter.parseBase64Binary("AmyNbm+7wJOjXv6GXI/vAEcl6gSX1AKxPr3GeExSYCiaxVaAeIvC23TWtp1/Vd/szfq1r1OzwrvkHeSSiskWMsMXaGQWUmiGtCnf9fqBU75T5PwNLCj4H9Nd5QENCMV/CFgVyGEi4X6Wlp18kqJPk/ooS6jLJwcWIe6DyrR1bQHl6YzKTfB4ACl2JEccBDz8dArKTrh4vFcQF4a+DtERm283Y2ue1DwG8lqWrYhsRO5v7vrW3lVpn5t25QXc+Y35zJ/il+lZJxKAgASwrKaq3G8RStdkeXCER23fSYhTmbLFqkFRWnmzu38hmLt5/iivUbm8NgELXP0SyQoYLMvfmA==");



            ByteBuffer dataBuffer = ByteBuffer.allocate(playerID.length+bundleID.length+8+salt.length)

                .put(playerID)

                .put(bundleID)

                .put(timestamp)

                .put(salt);





            Certificate cert = CertificateFactory.getInstance("X.509")

                    .generateCertificate(new URL("https://sandbox.gc.apple.com/public-key/gc-sb.cer").openConnection().getInputStream());





            Signature sig = Signature.getInstance("SHA1withRSA");

            sig.initVerify(cert);



            sig.update(dataBuffer);



            final boolean verify = sig.verify(sigToCheck);

            System.out.println("signature verifies: " + verify);                            



        } catch (Exception e) {            

            e.printStackTrace();

        }

    }        

}

There were no loss of bits in transferring data from the iOS 7 client to the server. This was verified by writing the binary bits to a file both from xCode and Java, generating their hex, and seeing if there were any diffs (note, the diffs just show the file name diffs):

$ xxd -i salt_Java.txt salt_java.xxd

$ xxd -i salt_xcode.txt salt_xcode.xxd

$ xxd -i sigToCheck_Java.txt sigToCheck_java.xxd

$ xxd -i sigToCheck_xcode.txt sigToCheck_xcode.xxd

$ diff salt_java.xxd salt_xcode.xxd 

1c1

< unsigned char salt_Java_txt[] = {

---

> unsigned char salt_xcode_txt[] = {

4c4

< unsigned int salt_Java_txt_len = 4;

---

> unsigned int salt_xcode_txt_len = 4;

$ diff sigToCheck_java.xxd sigToCheck_xcode.xxd 

1c1

< unsigned char sigToCheck_Java_txt[] = {

---

> unsigned char sigToCheck_xcode_txt[] = {

25c25

< unsigned int sigToCheck_Java_txt_len = 256;

---

> unsigned int sigToCheck_xcode_txt_len = 256;

$ 

I believe this fails because of the underlying Java libraries that Signature class uses, since the Objective-C solution listed here appears to successfully verify the same credentials.

My next attempt was to use the Java's [Cipher] and [MessageDigest] libraries instead of the [Signature] library, but this too fails. I suspect there are other steps missing before the signature digest bits can be checked with the provided signature bits.

final MessageDigest md = MessageDigest.getInstance("SHA1");

byte[] digest = md.digest(dataBuffer.array());

// RSA decrypt

Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");

cipher.init(Cipher.DECRYPT_MODE, cert);

byte[] decrypted = cipher.doFinal(sigToCheck);    



System.out.println("signature verifies: " + Arrays.equals(digest, decrypted));

Are there alternatives to verifying the digital signature or any gaps in the solutions posted above?

like image 695
milanbrahmbhatt Avatar asked Nov 01 '22 04:11

milanbrahmbhatt


1 Answers

The problem appears to be with the ByteBuffer you're passing to Signature.update(). If you pass the underlying array by changing

sig.update(dataBuffer);

to

sig.update(dataBuffer.array());

the verification appears to succeed. Based on the documentation for Signature.update(ByteBuffer), I suspect it's because it's trying to read from the last position you wrote to in the buffer, and not finding any data.

like image 117
Chris Barnes Avatar answered Nov 10 '22 18:11

Chris Barnes