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?
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With