Table of Contents
BigWorld guarantees the security of the client-server session in two important ways:
-
The login handshake is RSA-encrypted using a public key stored in the client resources.
-
The client-proxy channel is symmetrically encrypted using Blowfish.
As a result, it is impossible for an attacker to:
-
Steal a player's password
-
Hijack a player's session
-
Inject upstream packets into the player's traffic to disrupt his/her session
This security framework is provided to work out-of-the-box, and
BigWorld ships with an RSA keypair that we have pre-generated for you
(bigworld/res/server/loginapp.privkey
and
bigworld/res/loginapp.pubkey
).
Before making any kind of public game release, it is critical to
replace this keypair with a new keypair generated by your own company. As
all BigWorld clients receive the same default keypair BigWorld cannot
guarantee that this keypair is secure. Generating a new keypair with the
openssl
command-line utility (which should be
available in all modern Linux distributions) is simple:
-
Generate a new RSA keypair as follows:
$ openssl genrsa
[numbits]
> loginapp.privkeyBigWorld recommends using a 2048-bit key.
-
Strip out the public part of your keypair as follows:
$ openssl rsa -pubout < loginapp.privkey > loginapp.pubkey
The private key should be placed into your server game resources, and the public part should be placed in your client game resources.
Note
Ensure your private key is never shipped with your game client resources.
You may want to ship multiple public keys with your game client (for
instance, you may want to have a different key for each shard of your game
world). The BigWorld.connect()
function in the Client
API allows you to specify which public key to use when logging into the
server using the publicKeyPath
attribute of the
loginParams
object. Please see the Client API documentation
for more details.
The Client-Proxy Channel is encrypted using 128-bit Blowfish by
default. This encryption method was selected as it was the most secure,
high-performance symmetric cipher offered in the standard OpenSSL
distribution. Should you wish to use a different encryption algorithm, you
should be able to edit
src/lib/network/encryption_filter.cpp
to change the
encryption algorithm without needing to modify any header files.
You will probably want to leave the stream-padding operations in
EncryptionFilter::send()
and
EncryptionFilter::recv()
as they are; all you should
need to edit is the initialisation method
(EncryptionFilter::initKey()
) and the
encryption/decryption methods
(EncryptionFilter::encrypt()
and
EncryptionFilter::decrypt()
).
In previous versions of BigWorld, encryption support was not automatically provided, and the task of implementing it was left to each individual customer. In recognition of the importance of online security and the sensitivity of game data in today's online games, a standard implementation is now included with BigWorld.
This documentation used to describe the specifics of how to
implement your own Mercury::PacketFilter
to provide
end-to-end encryption for a Client-Proxy Channel. A lot of this
documentation is now irrelevant, however the description of how
PacketFilters work is still accurate and is retained for
completeness.
Packet filters do not allow arbitrary encryption algorithms to be used ‐ the algorithm implemented must work within the mechanics of Mercury. Mercury processes each data packet individually, and has mechanisms for coping with packet loss and similar problems. Since packet filters are called as the last stage of sending a packet, and as the first stage of receiving a packet, algorithms implemented within a packet filter should work under the normal constraints of UDP, which are:
-
Packets may be delivered out of order.
-
Packets may be delivered more than once.
For this reason, encryption algorithms that assume a continuous transport byte stream are not appropriate. The appropriate encryption algorithms are the ones that work on a single block (packet) at a time within a window, and include algorithms like DES block encryption (as used in the secure PPP protocol), Blowfish, TwoFish, or similar protocols used in wireless Ethernet technology (SSL is not appropriate, since it assumes a continuous stream). The previously mentioned block algorithms can support packet loss and prevent packet replayability, among other features.
Mercury takes care of the particulars of a UDP environment ‐ the packet filter only has to perform its filtering function. It uses sequence numbers of its own to discard duplicate packets (assuming that the packet filter itself has not discarded the packet). It also detects dropped packets, and resends them if they contained reliable information (so the packet filter should not do this). When Mercury needs to resend messages, it will sometimes resend the whole packet unmodified, and at other times (if there is space) it will piggyback just the reliable messages of the dropped packet into the body of the next new packet. Packet filters must be able to cope with both of these types of resending.
Packet filters are always associated with channels, like the ones between a proxy and a client.
For every sent packet associated with a Channel object, the send method of the associated PacketFilter is called, with the packet as one of the parameters. This happens after all other processing on the packet. Similarly, the recv method of the PacketFilter instance is called for every packet that is received over the channel by the Nub, before any other processing occurs on it. This method should undo any modifications made to the packet by the send method, and then call the base class PacketFilter::recv method.
Since packets may be received out of order, the PacketFilter instance must be able to reconstruct any packet received within the Channel's window. Duplicate packets may also be received, and these must also be reconstructed when within the window, so that they can be acknowledged (in case the ACK for the original packet was lost).
Note that a PacketFilter must not modify a packet to be sent ‐ or at least if it does so, it then must undo the modifications afterwards. If it were to leave a packet modified, and that packet needed to be resent (or partially resent), then it would be filtering the data twice. Depending on the nature of the modifications being made to the Packet's data, it may make sense to do the desired changes, or it may make more sense to simply grab a new Packet from the PacketPool, write the filtered data to that packet, and then send it. This notice does not apply to packets that are received ‐ the PacketFilter may modify received packets in-place if it so chooses, especially if it is efficient to do so.
The PacketFilter base class' send implementation sends the packets over the Nub's socket with the usual accounting, error checking, and retries.
Do not use the Nub's socket directly. The PacketFilter base class' recv implementation submits the packet for normal internal processing. If you do not call it, then you must give the packet back to the PacketPool (with PacketPool::give), or the packet will be leaked.
New packets may be obtained and old ones returned at any time via the PacketPool object. PacketFilter instances may use and store packets for any purpose that they see fit. Also, the PacketFilter base class' methods send and recv may be called as many times as desired from the corresponding derived methods send and recv, if that is what the filter needs to do.
Normally, when messages are added to a bundle, the full size of the packet may be used. This would deny many potential filtering uses, which might want to add data to packets after this. The virtual method maxSpareSize may be implemented in this case to specify the minimum amount of space to leave spare in each packet (i.e., the maximum amount of space required by the PacketFilter).