Thursday, December 16, 2010

SmartFoxServer Parse and Store Unity3D public key

Last time we left off we sent our message to the Extension, but it doesn't know what to do with it so nothing happens. Today we will create the information necessary to capture the public, create a public key for the client, and send that back to the client. If any of you have taken a look at the FPS tutorial you will recognize 2 of the classes from that source, World and RoomHelper.

Lets start by creating the packages we will be using. Open NetBeans and right click on our package called [domain.domainname.gamename] and New -> Java Package. We want to create packages handlers, util, and core. Handlers is like the Unity3D handlers, they are classes we have built to handle messages. Util will contain the RoomHelper and any other utility classes that do not fall into other packages. Core will contain anything that is central to our game, in this case World and [GameName]Account.

Lets go ahead and start by creating RoomHelper in the util package. Right click the package and New -> Java Class and call it RoomHelper. It contains the following:


package [domain.domainname.gamename].util;


import com.smartfoxserver.v2.entities.Room;
import com.smartfoxserver.v2.extensions.BaseClientRequestHandler;
import com.smartfoxserver.v2.extensions.BaseServerEventHandler;
import com.smartfoxserver.v2.extensions.SFSExtension;


import [domain.domainname.gamename].[GameName]Extension;
import [domain.domainname.gamename].simulation.World;


// Helper methods to easily get current room or zone and precache the link to ExtensionHelper
public class RoomHelper {


public static Room getCurrentRoom(BaseClientRequestHandler handler) {
return handler.getParentExtension().getParentRoom();
}


public static Room getCurrentRoom(SFSExtension extension) {
return extension.getParentRoom();
}


public static World getWorld(BaseClientRequestHandler handler) {
[GameName]Extension ext = ([GameName]Extension) handler.getParentExtension();
return ext.getWorld();
}


public static World getWorld(BaseServerEventHandler handler) {
[GameName]Extension ext = ([GameName]Extension) handler.getParentExtension();
return ext.getWorld();
}
}


As you can see, it gets our room information, but we will be using it almost exclusively for getting our world so we can get to our accounts. Since it is referencing World lets go ahead and create it now. Make sure you create it under [domain.domainname.gamename].core
/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package [domain.domainname.gamename].core;

import [domain.domainname.gamename].[GameName]Extension;
import com.smartfoxserver.v2.entities.User;
import java.security.PublicKey;
import java.util.ArrayList;
import java.util.List;

/**
 *
 * @author richach7
 */
public class World {


    private [GameName]Extension extension; // Reference to the server extension
    // Players
    private List<[GameName]Account> players = new ArrayList<[GameName]Account>();

    public World([GameName]Extension extension) {
        this.extension = extension;
    }

    public List<[GameName]Account> getPlayers() {
        return players;
    }

    // Add new player if he doesn't exist, or resurrect him if he already added
    public boolean addPlayer(User user, ISFSObject data) {
        [GameName]Account player = getPlayer(user);
        boolean didAddPlayer = false;
        try
        {
            if (player == null)
            {
                    player = new [GameName]Account(user, data);

                players.add(player);
                didAddPlayer = true;
            }
            else 
            {
                player.SetPublicKey(data);
            }
        }
        catch(Exception e)
        {
            extension.trace(e);
        }
        finally
        {
            return didAddPlayer;
        }
    }

    // Gets the player corresponding to the specified SFS user
    public [GameName]Account getPlayer(User u) {
        for ([GameName]Account player : players) {
            if (player.getSfsUser().getId() == u.getId()) {
                return player;
            }
        }

        return null;
    }

    // When user lefts the room or disconnects - removing him from the players list
    public void userLeft(User user) {
        [GameName]Account player = this.getPlayer(user);
        if (player == null) {
            return;
        }
        players.remove(player);
    }
}
You will see that the world class is the class that links SmartFox's User class to our Account class. We haven't created the Account class so don't worry that you are seeing errors with it missing. We will be creating it shortly. The Account class will be the link to our user and will contain our server's public and private key and will also contain the functions to get our public key from our Unity3D message and create an SFSObject that contains the server's public key for this user. This setup allows our Account to have a unique key making player spoofing impossible. Lets start with the basics for the [GameName]Account class by creating it in the core package.
/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package [domain.domainname.gamename].core;

import com.smartfoxserver.v2.entities.User;
import com.smartfoxserver.v2.entities.data.ISFSObject;
import com.smartfoxserver.v2.entities.data.SFSObject;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.RSAPrivateKeySpec;
import java.security.spec.RSAPublicKeySpec;
import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;

/**
 *
 * @author richach7
 */
public class [GameName]Account {

    private User sfsUser; // SFS user that corresponds to this player
    private PublicKey pubKey;
    private RSAPrivateKeySpec privKey;
    private RSAPublicKeySpec serverPubKey;
    private Cipher cipher;
    public User getSfsUser() {
        return sfsUser;
    }


    public [GameName]Account(User sfsUser, ISFSObject data) throws NoSuchAlgorithmException, InvalidKeySpecException {
        this.sfsUser = sfsUser;
        SetPublicKey(data);

        try {
            KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
            kpg.initialize(2048);
            KeyPair kp = kpg.genKeyPair();
            KeyFactory fact = KeyFactory.getInstance("RSA");
            serverPubKey = fact.getKeySpec(kp.getPublic(),
                    RSAPublicKeySpec.class);
            privKey = fact.getKeySpec(kp.getPrivate(),
                    RSAPrivateKeySpec.class);
            readyCipher();
        } catch (Exception e) {
            //trace(e);
        }

    }

    public void SetPublicKey(ISFSObject data) throws NoSuchAlgorithmException, InvalidKeySpecException {
                ISFSObject keyData = data.getSFSObject("key");
                RSAPublicKeySpec keySpec = new RSAPublicKeySpec(new BigInteger(keyData.getByteArray("mod")), new BigInteger(keyData.getByteArray("exp")));
                KeyFactory fact = KeyFactory.getInstance("RSA");
                pubKey = fact.generatePublic(keySpec);
    }

    public RSAPublicKeySpec GetServerPubKey()
    {
        return serverPubKey;
    }

    public RSAPrivateKeySpec GetPrivateKey()
    {
        return privKey;
    }

    public PublicKey GetPublicKey()
    {
        return pubKey;
    }

    public Cipher GetCipher()
    {
        return cipher;
    }

}
So this class when it gets created is passed a SmartFox User and the SFSObject that contains our Unity3D client's public key. It also goes ahead and generates our server's public/private key pair when we get the client's public key.

Next time we will cover sending a response back to the client and getting that response completing our round trip from client to server and back to client.

4 comments:

Om3n said...

Amazing work, and thank you. I'm having some issues with this section of code.

public Account getPlayer(User u) {
for (Account player : players) {
if (player.getSfsUser().getId() == u.getId()) {
return player;
}
}

return null;
}

It gives this message with the error: incompatible types
required: com.somename.accounts.core.Account
found: java.lang.Object

Unknown said...

I see what happened. The problem is that when we defined players, Blogger thought it was using a tag. Your creation of players should be List<Account> players = new ArrayList<Account>;

Unknown said...

I have updated the code, any place you saw List or ArrayList, you should now see List<[GameName]Account> or ArrayList<[GameName]Account>

Om3n said...

Ah! I see that now. I spent hours looking at that.. Couldn't quite figure it out. Again thank you. Its good to see some tutorials with some substance out there.