Thursday, December 16, 2010

Unity3D Public/Private Key

Last time we covered how you can use the SmartFox2.dll to connect to your SmartFox Server. Today we will be covering the setup necessary to use Public/Private Key Encryption. I encourage you to do a little research on it. In short it is a way for our client to wrap up data and send it to the server without others being able to see the content. This is really important for sensitive data like passwords. It isn't as important for things like locations, reloads, firing, etc, however it is a big deterrent for someone trying to spoof packets from other players and either getting them kicked or banned.

This first part is going to be a little complex. We want a way to receive messages from the server that we create. This includes things like retrieving transforms, character information, quest data, really anything that the server would send to the client.

If you have looked through the FPS example, you will see a giant if-else block naming out all the messages we could receive in the OnExtensionResponse function. It's hideous, it's ugly, it's just plain messy. That's not to say its wrong, but we can do better. 

We are going to create a delegate. Delegates are a very complex concept so you can look it up later if you want. To get you started, Microsoft explains it here. We begin by opening ConnectionHandler and inserting this chunk of code between the debug = true and the Awake() function.

    protected delegate void ExtensionHandler(ISFSObject data);

    protected Dictionary <string, ExtensionHandler> handlers;

This creates a list of functions of type ExtensionHandler which we can look up by the string provided as the key.

Next we need to let SmartFox know we what function we wish to use to handle the SFSEvent.EXTENSION_RESPONSE so we need to go into the Awake() function and at the bottom add:
smartFox.AddEventListener(SFSEvent.EXTENSION_RESPONSE, OnExtensionResponse);
Again, this tells SmartFox that whenever we receive and extension response, use the OnExtensionResponse function. Extension Responses are any messages we create ourselves. Next we will be creating our OnExtensionResponse. This will look through our dictionary of handlers to try and find a handler that can act on the command passed in by SmartFox.
    // This method handles all the responses from the server
    protected void OnExtensionResponse(BaseEvent evt)
    {
        try
        {
            string cmd = (string)evt.Params["cmd"];
            ISFSObject dt = (SFSObject)evt.Params["params"];

            ExtensionHandler handler;

            if (handlers.TryGetValue(cmd, out handler))
            {
                handler(dt);
            }
            else
            {
                Debug.LogError("Got unhandled cmd: " + cmd);
            }
        }
        catch (Exception e)
        {
            Debug.Log("Exception handling response: " + e.Message + " >>> " + e.StackTrace);
        }

    }
You can see that we get the command, then we get the SmartFox object that we will have been passed from the server. We try to get a handler that is looking for the specific command. If it finds it, it calls it passing in the  object from SmartFox, if it doesn't, we log an error in Unity3d telling us what command was passed to us so we can easily find out what went wrong.

We are now ready to build up our first message to the server, sending our public key. We will start by making a class that will contain functions that allow us to build an SFSObject from our class and another to turn an SFSObject back into data for our class. Start by creating a class called EncryptionProvider in Common\Scripts and open it up to edit it. Like the SmartFox Connection, this will be a singleton.
using System;
using Sfs2X.Entities.Data;
using System.Security.Cryptography;
using Sfs2X.Util;
using UnityEngine;

public class EncryptionProvider
{
    RSACryptoServiceProvider ClientRSA;
    RSACryptoServiceProvider ServerRSA;

    // Singleton pattern
    private static EncryptionProvider provider;
    public static EncryptionProvider GetInstance()
    {
        if(provider == null)
        {
            provider = new EncryptionProvider();
        }
        return provider;
    }


    public bool HasServerPK
    {
        get;
        protected set;
    }


    private EncryptionProvider()
    {
        ClientRSA = new RSACryptoServiceProvider();
        HasServerPK = false;
    }

}
This is our base class we will use to store the server public key and our public and private keys. In the constructor we create our Encryption provider and we set a flag to tell us if we have the server's key yet. Now we will add the function that creates an SFSObject from our ClientRSA:
public ISFSObject ClientPublicKeyToSFSObject()
{
ISFSObject tr = new SFSObject();
tr.PutByteArray("mod", new ByteArray(ClientRSA.ExportParameters(false).Modulus));
tr.PutByteArray("exp", new ByteArray(ClientRSA.ExportParameters(false).Exponent));
ISFSObject data = new SFSObject();
data.PutSFSObject("key", tr);
return data;
}
In order to recreate a key we need the modulus and exponent of the public key. By passing false to ExportParameters we get the public key, if we were to pass true, we would get the private key.

Now lets send it to the server. Open ConnectionHandler and lets add the provider and the code to get it set up. Under the handlers declaration add:
    protected EncryptionProvider provider;
At the bottom of the awake function add:
        provider = EncryptionProvider.GetInstance();
Now open up LobbyGUI and go to the OnLogin function. In the empty else block add:
            ExtensionRequest request = new ExtensionRequest("publickey", provider.ClientPublicKeyToSFSObject());
            smartFox.Send(request);
Congratulations, you just sent your first message to the server. Next time we will modify the [GameName]Extension code to receive the message and send back the server's public key.

No comments: