question

Damir Saifullin avatar image
Damir Saifullin asked

Unity Game Center Secure Authentication problem

Hello, we are trying to add Secure Game Center authentication in Unity for iOS users on PlayFab. There are really no exact documentation on how to do it properly. We've tried many things and still have issues.

First of all we are adding Gamekit library (example was here https://gist.github.com/BastianBlokland/bbc02a407b05beaf3f55ead3dd10f808 ) in Unity. The example was outdated so we've used a new method fetchItems:

#import <Foundation/Foundation.h>
#import <GameKit/GameKit.h>


typedef void (*fetchItemsCallback)(const char * publicKeyUrl, const char * signature, int signatureLength, const char * salt, int saltLength, const uint64_t timestamp, const char * error);


extern void fetchItems(fetchItemsCallback callback) {
    
    GKLocalPlayer * localPlayer = [GKLocalPlayer localPlayer];


    NSLog(@"LocalPlayer: %@", localPlayer.playerID);
    if (@available(iOS 13.5, *)) {
        [localPlayer fetchItemsForIdentityVerificationSignature:^(NSURL * _Nullable publicKeyURL, NSData * _Nullable signature, NSData * _Nullable salt, uint64_t timestamp, NSError * _Nullable error) {
            
            NSLog(@"Received 'fetchItems' callback, error: %@", error.description);
            
            // Create a pool for releasing the resources we create
            @autoreleasepool {
                
                // PublicKeyUrl
                const char * publicKeyUrlCharPointer = NULL;
                if (publicKeyURL != NULL)
                {
                    const NSString * publicKeyUrlString = [[NSString alloc] initWithString:[publicKeyURL absoluteString]];
                    publicKeyUrlCharPointer = [publicKeyUrlString UTF8String];
                }
                
                // Signature
                const char * signatureBytes = [signature bytes];
                int signatureLength = (int)[signature length];
                
                // Salt
                const char * saltBytes = [salt bytes];
                int saltLength = (int)[salt length];
                
                // Error
                const NSString * errorString = error.description;
                const char * errorStringPointer = [errorString UTF8String];


                NSLog(@"salt %@", salt);
                NSLog(@"signature %@", signature);
                
                // Callback
                callback(publicKeyUrlCharPointer, signatureBytes, signatureLength, saltBytes, saltLength, timestamp, errorStringPointer);
            }
    }];
    } else {
        // Fallback on earlier versions
        
    }
}


And this is a C# script we are using in Unity:

using System;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Text;
using AOT;
using PlayFab;
using PlayFab.ClientModels;
using SingularityLab;
using SingularityLab.Assets._RobotStars.Scripts.GameNetwork.GamePhoton.GameAuthentication;
using SingularityLab.Assets._RobotStars.Scripts.GameNetwork.GooglePlayGames;
using UnityEngine;


namespace SingularityLab.Assets._RobotStars.Scripts.GameNetwork.GamePhoton.GameAuthentication
{
    public class PlayFabAuthenticator : BasePlayfabAuthenticator
    {
        public PlayFabAuthenticator(IAuthenticationListener authenticationListener) : base(authenticationListener)
        {


        }


        public override void Authenticate()
        {
#if UNITY_ANDROID && !UNITY_EDITOR
        Debug.Log("LoginWithGooglePlayGamesAuthenticator");


        GooglePlayGamesAuthenticator googlePlayGamesAuthenticator = new GooglePlayGamesAuthenticator();


        googlePlayGamesAuthenticator.Authenticate(OnLoginSuccess, OnPlayFabError);


#elif UNITY_IOS && !UNITY_EDITOR
            IOSGameCenterAuthenticator iOSGameCenterAuthenticator = new IOSGameCenterAuthenticator();
            iOSGameCenterAuthenticator.Initialize();
#else
            Debug.Log("LoginWithCustomID");
            PlayFabClientAPI.LoginWithCustomID(
                 new LoginWithCustomIDRequest()
                 {
                     CreateAccount = true,
                     CustomId = GetDeviceId()
                 }, OnLoginSuccess, OnPlayFabError);
#endif
        }


        protected override void OnLoginSuccess(LoginResult loginResult)
        {
            _authenticationListener.OnAuthenticationSuccess(new LoginData(loginResult.PlayFabId));
        }


        protected override void OnPlayFabError(PlayFabError playFabError)
        {
            ApiTestMonoDebug.Instance.ShowLog(playFabError.ErrorDetails + " !!! " + playFabError.ErrorMessage + " !!! " + playFabError.Error.ToString());
        }
    }
}




public class IOSGameCenterAuthenticator
{
#if UNITY_IOS
    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    private delegate void fetchItemsCallback(
    string publicKeyUrl,
    IntPtr signaturePointer, int signatureLength,
    IntPtr saltPointer, int saltLength,
    ulong timestamp,
    string error);


    [DllImport("__Internal")]
    private static extern void fetchItems(
        [MarshalAs(UnmanagedType.FunctionPtr)] fetchItemsCallback callback);
    private static string ByteArrayToString(byte[] data)
    {
        StringBuilder sb = new StringBuilder();


        foreach (byte b in data)
        {


            sb.Append(b.ToString("X"));
        }


        return sb.ToString();
    }
    // Note: This callback has to be static because Unity's il2Cpp doesn't support marshalling instance methods.
    [MonoPInvokeCallback(typeof(fetchItemsCallback))]
    private static void OnfetchItemsGenerated(
        string publicKeyUrl,
        IntPtr signaturePointer, int signatureLength,
        IntPtr saltPointer, int saltLength,
        ulong timestamp,
        string error)
    {
        // Create a managed array for the signature
        byte[] signature = new byte[signatureLength];
        Marshal.Copy(signaturePointer, signature, 0, signatureLength);


        // Create a managed array for the salt
        byte[] salt = new byte[saltLength];
        Marshal.Copy(saltPointer, salt, 0, saltLength);


        string stringSalt = ByteArrayToString(salt);
        string stringSignature = ByteArrayToString(signature);


        UnityEngine.Debug.Log($"localUser id: {Social.localUser.id}");
        UnityEngine.Debug.Log($"publicKeyUrl: {publicKeyUrl}");
        UnityEngine.Debug.Log($"signature: {stringSignature}");
        UnityEngine.Debug.Log($"salt: {stringSalt}");
        UnityEngine.Debug.Log($"timestamp: {timestamp.ToString()}");
        UnityEngine.Debug.Log($"error: {error}");


        PlayFabClientAPI.LoginWithGameCenter(new LoginWithGameCenterRequest()
        {
            CreateAccount = true,
            PlayerId = Social.localUser.id,
            Timestamp = timestamp.ToString(),
            PublicKeyUrl = publicKeyUrl,
            Salt = stringSalt,
            Signature = stringSignature
        }, (result) =>
        {
            Debug.Log("Signed In as " + Social.localUser.userName);
            ApiTestMonoDebug.Instance.ShowLog("Signed In as " + Social.localUser.userName);
        },
        (e) => { Debug.Log($"Signed In Fail: Error: {e.Error} , ErrorDeail: {e.ErrorDetails} , ErrorMessage: {e.ErrorMessage}"); });
    }
#endif
    public void Initialize()
    {
        Social.localUser.Authenticate((bool success) => {
            if (success)
            {
#if UNITY_IOS
                Debug.Log($"Authenticate local user success");
                fetchItems(OnfetchItemsGenerated);
#endif
            }
            else
            {
                Debug.Log("Authenticate local user error");
            }


        });
    }
}


Unity version 2020.3 LTS

We are received either a 500 error from Playfab or Signed In Fail: Error: GameCenterAuthenticationFailed, ErrorDetail: ErrorMessage: Authentication failed, Signature does not match the authentication request data

And this is what we send ( tried also with Postman ) :

localUser id: T:_d52bf2fee87fb96d637a5b4a9cf6e1d6
IOSGameCenterAuthenticator:OnfetchItemsGenerated(String, IntPtr, Int32, IntPtr, Int32, UInt64, String)


publicKeyUrl: https://static.gc.apple.com/public-key/gc-prod-5.cer
IOSGameCenterAuthenticator:OnfetchItemsGenerated(String, IntPtr, Int32, IntPtr, Int32, UInt64, String)


signature: 3EC757F4318A16331B729769EEF92997A3A9BD22E928A9B6AA4218D1E8F95293992A0686999AC6CA390C96CE982234AAD388AD43C685FCE7985A2D68DC5A1C5BB954F12E343AC98A446A33337F06ECFBFA574B086374261191BFFEEFA8A3D2D3CF8CE3B76DFB8ACA9A89483B215932C9CFCA7669B7E7CF8A17ED2FF20A816C62D1D355D954A7E8AD91E75EF5389B747CD93D1B1785ABCE4782BDC9D12FA65ED64E97992FFF8C57D1243541566BDCD58A05E900BEE79280DEA1F474AA22A0FBC2BCE0F2FB7575E33B8EC42A919D3E0A6576C3B4E644E913E2219F9D48EB7BBD0268C2B249C11D2B94DFB7B6636E2466FC7E1429E1FF570
IOSGameCenterAuthenticator:OnfetchItemsGenerated(String, IntPtr, Int32, IntPtr, Int32, UInt64, String)


salt: 953D7A67
IOSGameCenterAuthenticator:OnfetchItemsGenerated(String, IntPtr, Int32, IntPtr, Int32, UInt64, String)


timestamp: 1620982435036
IOSGameCenterAuthenticator:OnfetchItemsGenerated(String, IntPtr, Int32, IntPtr, Int32, UInt64, String)
10 |1200

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.

1 Answer

·
Damir Saifullin avatar image
Damir Saifullin answered

We made it!

Just needed to convert the Signature to Base64

10 |1200

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.

Write an Answer

Hint: Notify or tag a user in this post by typing @username.

Up to 2 attachments (including images) can be used with a maximum of 512.0 KiB each and 1.0 MiB total.