2

Is there any documentation from Apple that covers the format or algorithms used to generate the file returned by - [GKLocalPlayer generateIdentityVerificationSignatureWithCompletionHandler:]?

I've seen a few questions on SO about the basic flow of authenticating a user, which seem to suggest the file is a .cer file (application/pkix-cert), and that it uses the DER file format, but I can't find anything official from Apple.

I'm looking to verify the cert in Java, if anyone has any examples, that would be much appreciated as well.

Community
  • 1
  • 1
TizzyTool
  • 233
  • 2
  • 5

2 Answers2

2

ios

[localPlayer generateIdentityVerificationSignatureWithCompletionHandler:^(NSURL *publicKeyUrl, NSData *signature, NSData *salt, uint64_t timestamp, NSError *error) {
                if(error != nil){
                    //login failed.
                }else{
                    NSLog(@"signature : %@", [signature base64Encoding]);
                    NSLog(@"salt : %@", [salt base64Encoding]);
                    NSLog(@"PUBLIC_KEY_URL : %@", publicKeyUrl);
                    NSLog(@"signature : %@", [signature base64EncodedStringWithOptions:0]);
                    NSLog(@"salt : %@", [salt base64EncodedStringWithOptions:0]);
                    NSLog(@"timestamp : %lld", timestamp);

                    NSLog(@"Gamecenter login success.");

                    NSMutableData *payload = [[NSMutableData alloc] init];
                    [payload appendData:[[GKLocalPlayer localPlayer].playerID dataUsingEncoding:NSUTF8StringEncoding]];
                    [payload appendData:[[[NSBundle mainBundle] bundleIdentifier] dataUsingEncoding:NSUTF8StringEncoding]];
                    uint64_t timestampBE = CFSwapInt64HostToBig(timestamp);
                    [payload appendBytes:&timestampBE length:sizeof(timestampBE)];
                    [payload appendData:salt];

                    NSString *ext_id = localPlayer.playerID;
                    NSString *encodedSignedData = [payload base64EncodedStringWithOptions:0];
                    NSLog(@"SIGNEDDATA : %@", encodedSignedData);
                    NSString *encodedSignature  = [signature base64Encoding];
                    NSString *publicKeyUrlStr   = [publicKeyUrl absoluteString];

                }
            }];

java code is..

public static String makeEncodedSignedDataForAppleVerify(String player_id, String bundle_id, String timestamp, String salt){
    return new String(Base64.encodeBase64(concat(player_id.getBytes(),
            bundle_id.getBytes(),
            ByteBuffer.allocate(8).putLong(Long.parseLong(timestamp)).array(), 
            Base64.decodeBase64(salt))));
}

public static boolean gamecenterUserVerify(String publicKeyUrl, String signedData, String signature){
    try{
        URL url = new URL(publicKeyUrl);

        HttpURLConnection urlConn = (HttpURLConnection) url.openConnection();
        HttpURLConnection httpConn = (HttpURLConnection) urlConn;
        httpConn.setAllowUserInteraction(false);
        httpConn.connect();

        InputStream in = httpConn.getInputStream();
        BufferedInputStream bis = new BufferedInputStream(in);
        ByteArrayBuffer baf = new ByteArrayBuffer(99999);
        int read = 0;
        int bufSize = 512;
        byte[] buffer = new byte[bufSize];
        while(true){
            read = bis.read(buffer);
            if(read==-1){
                break;
            }
            baf.append(buffer, 0, read);
        }

        byte[] bytes =baf.toByteArray();
        bis.close();
        in.close();
        httpConn.disconnect();

        CertificateFactory cf =   CertificateFactory.getInstance("X509");
        X509Certificate c = (X509Certificate) 
        cf.generateCertificate(new ByteArrayInputStream(bytes) );

        PublicKey key22 = c.getPublicKey();

        byte[] result = Base64.decodeBase64(signedData);//;
        byte[] decodedSignature = Base64.decodeBase64(signature);

        Signature sig;
        try {
            sig = Signature.getInstance("SHA256withRSA");
            sig.initVerify(key22);
            sig.update(result);
            if (!sig.verify(decodedSignature)) {
                return false;
            }else{
                return true;
            }
        } catch (NoSuchAlgorithmException e) {
           e.printStackTrace();
        } catch (InvalidKeyException e) {
           e.printStackTrace();
        } catch (SignatureException e) {
           e.printStackTrace();
        }
    }catch(Exception e){
        e.printStackTrace();
    }

    return false;
}

static byte[] concat(byte[]...arrays)
{
    // Determine the length of the result array
    int totalLength = 0;
    for (int i = 0; i < arrays.length; i++)
    {
        totalLength += arrays[i].length;
    }

    // create the result array
    byte[] result = new byte[totalLength];

    // copy the source arrays into the result array
    int currentIndex = 0;
    for (int i = 0; i < arrays.length; i++)
    {
        System.arraycopy(arrays[i], 0, result, currentIndex, arrays[i].length);
        currentIndex += arrays[i].length;
    }

    return result;
}
Mayank Jain
  • 5,663
  • 7
  • 32
  • 65
chamnassi
  • 21
  • 2
0

I cannot comment to chamnassi's solution due to low reputation: chamnassi's answer works great

In addition, the following check should be added: verify that the certificate is from Apple. The quick & dirty solution is to verify the URL originates from "apple.com":

// verify the URL: needs to have ".apple.com/" inside!
String path = publicKeyUrl;
path = path.substring(path.indexOf("://") + 3);  // cut protocol, e.g. "https://" from front
path = path.substring(0, path.indexOf("/"));     // cut the URL-path part, so we only have something list "whatever.apple.com"!
if (!path.toLowerCase().endsWith(".apple.com")) {
    // invalid certificate: not from Apple site!
    --> throw error
}
Jean-François Fabre
  • 137,073
  • 23
  • 153
  • 219
noblemaster
  • 404
  • 4
  • 8