Wednesday, June 22, 2011

Foray into Photon - Part 13 - Exchanging keys for encryption

So before we create our login, we need to take the time to handle encryption. There are several file modifications we must perform to pass along when we want to encrypt an operation. We begin with IGameState. The last function is SendOperation, this now changes to:


    void SendOperation(Game gameLogic, OperationCode operationCode, Hashtable parameter, bool sendReliable, byte channelId, bool encrypt);

Because of this change we have to modify Disconnected, WaitingForConnect, and Connected. Modify each of those classes so they also have bool encrypt at the end. Lastly we need to modify the code inside Connected.SendOperation to be the following:

    public void SendOperation(Game gameLogic, AegisBornCommon.OperationCode operationCode, System.Collections.Hashtable parameter, bool sendReliable, byte channelId, bool encrypt)
    {
        gameLogic.Peer.OpCustom((byte)operationCode, parameter, sendReliable, channelId, encrypt);
    }

Disconnected and WaitingForConnect only need the first change because they have no code in send operation.

With this change in place we need to modify Game.SendOp to pass along an encrypt:

    public void SendOp(OperationCode operationCode, Hashtable parameter, bool sendReliable, byte channelId, bool encrypt)
    {
        _stateStrategy.SendOperation(this, operationCode, parameter, sendReliable, channelId, encrypt);
    }

Now that we are set up to pass along encrypted messages, we need to exchange keys with the server. The best place for this would be in Game.SetConnected because it ensures we are connected to the server and we can handle the response in Connected's handling code. Here is the new function:

    public void SetConnected()
    {
        _stateStrategy = Connected.Instance;
        _listener.OnConnect(this);
        _peer.OpExchangeKeysForEncryption();
    }

    public delegate void AfterKeysExchanged();
    public AfterKeysExchanged afterKeysExchanged;

    public void NotifyKeysExchanged()
    {
        if (afterKeysExchanged != null)
        {
            afterKeysExchanged();
        }
        _listener.LogInfo(this, "Keys successfully exchanged");
    }

In Connected.cs we need to handle the response as it comes back from the server in OnOperationResponse:

        var handled = false;

        switch(operationCode)
        {
            case OperationCode.ExchangeKeysForEncryption:
                gameLogic.Peer.DeriveSharedKey((byte[])returnValues[(byte)ParameterCode.ServerKey]);
                gameLogic.NotifyKeysExchanged();
                handled = true;
                break;
        }

        if (!handled)
        {
            gameLogic.OnUnexpectedPhotonReturn(returnCode, operationCode, returnValues);
        }

At this point you will notice we need to add a few things to AegisBornCommon. Lets do that now. In OperationCode.cs we need to add the ExchangeKeysForEncryption code:

        ///
        /// Code for exchanging keys using PhotonPeer.OpExchangeKeysForEncryption
        ///
        ExchangeKeysForEncryption = 95,

Then we need to add a couple values to the ParameterCode.cs file:

        ///
        /// Client key parameter used to establish secure communication.
        ///
        ClientKey = 16,

        ///
        /// Server key parameter used to establish secure communication.
        ///
        ServerKey = 17,

Once you recompile and ensure the new dll is in the Plugins directory in your Unity3d project the compile errors will disappear and you will be ready to implement the server portion of our key exchange.

First we need to create an operation to handle the exchange. I created a folder in AegisBorn called Operations and inside I created the EstablishSecureCommunicationOperation:

using AegisBornCommon;
using Photon.SocketServer;
using Photon.SocketServer.Rpc;

public class EstablishSecureCommunicationOperation : Operation
{
    public EstablishSecureCommunicationOperation(OperationRequest operationRequest)
        : base(operationRequest)
    {
    }

    ///
    /// Gets or sets the clients public key.
    ///
    [RequestParameter(Code = (short)ParameterCode.ClientKey, IsOptional = false)]
    public byte[] ClientKey { get; set; }

    ///
    /// Gets or sets the servers public key.
    ///
    [ResponseParameter(Code = (short)ParameterCode.ServerKey, IsOptional = false)]
    public byte[] ServerKey { get; set; }
}

This class' job is to take the information from the client, create a key, and send back a key to the client that the client will use when sending encrypted messages. Next we need to add our code to the dispatcher AegisBornPeer:

        [Operation(OperationCode = (byte)OperationCode.ExchangeKeysForEncryption)]
        public OperationResponse OperationKeyExchange(Peer peer, OperationRequest request)
        {
            var operation = new EstablishSecureCommunicationOperation(request);
            if (operation.IsValid == false)
            {
                return new OperationResponse(request, (int)ErrorCode.InvalidOperationParameter, operation.GetErrorMessage());
            }

            // initialize the peer to support encrytion
            operation.ServerKey = peer.PhotonPeer.InitializeEncryption(operation.ClientKey);

            // publish the servers public key to the client
            return operation.GetOperationResponse(0, "OK");
        }

Thanks to the work we did earlier, this operation is automatically handled. It initializes the encryption and returns an OK response to the client. At this time I also went ahead and added the following code to the ErrorCode.cs file which comes directly from the Exit Games Demo:

        ///
        /// The parameter out of range.
        ///
        ParameterOutOfRange = 51,

        ///
        /// The operation not supported.
        ///
        OperationNotSupported,

        ///
        /// The invalid operation parameter.
        ///
        InvalidOperationParameter,

        ///
        /// The invalid operation.
        ///
        InvalidOperation, 

With all this in place, when you click connect, you will get a message says Keys successfully exchanged. At this point we are ready to send an encrypted message to the server. You will notice that I used a switch statement in Connected.OnOperationResponse. We will be removing this and adding Operation Handlers in the near future. If you pull down the latest GitHub code, I already have them in place as well as what we will cover in the next blog, which is sending a login operation.

3 comments:

DocA said...

Hey,

instead of "keys exchanged" I get this exception: "The requested feature is not implemented."

any idea whats wrong here?

Unknown said...

I am wondering which version of the client you are using. I know in January they implemented encryption. Have you pulled down new versions? I know for a while they said it was implemented server side, but not client side.

DocA said...

Hey dragagon,

yes that was it, the client on my end was still implemented with Throw NotImplementedException calls on different loglevels, after removing/changing these it worked.

Thanks a lot!

DocA