diff --git a/examples/server.js b/examples/server.js index 3c8ba0d..4646901 100644 --- a/examples/server.js +++ b/examples/server.js @@ -3,7 +3,6 @@ var mc = require('../'); var yellow = '§e'; var options = { - 'online-mode': false, motd: 'Vox Industries', 'max-players': 127, port: 25565, diff --git a/index.js b/index.js index 2ea887c..3edcf2a 100644 --- a/index.js +++ b/index.js @@ -30,13 +30,16 @@ function createServer(options) { var checkTimeoutInterval = options.checkTimeoutInterval || 4 * 1000; var motd = options.motd || "A Minecraft server"; var onlineMode = options['online-mode'] == null ? true : options['online-mode']; - assert.ok(! onlineMode, "online mode for servers is not yet supported"); + var encryptionEnabled = options['enable-encryption'] == null ? true : options['enable-encryption']; + + var serverKey = ursa.generatePrivateKey(1024); var server = new Server(options); server.maxPlayers = maxPlayers; server.on("connection", function(client) { client.once(0xfe, onPing); - client.on(0x02, onHandshake); + client.once(0x02, onHandshake); + client.once(0xFC, onEncryptionKeyResponse); client.on('end', onEnd); var keepAlive = false; @@ -46,6 +49,8 @@ function createServer(options) { var keepAliveTimer = null; var loginKickTimer = setTimeout(kickForNotLoggingIn, kickTimeout); + var hash; + function kickForNotLoggingIn() { client.end('LoginTimeout'); } @@ -95,9 +100,71 @@ function createServer(options) { } function onHandshake(packet) { - assert.ok(! onlineMode); - loggedIn = true; client.username = packet.username; + if (onlineMode) { + serverId = crypto.randomBytes(4).toString('hex'); + } else { + serverId = '-'; + } + if (encryptionEnabled) { + client.verifyToken = crypto.randomBytes(4); + var publicKeyStrArr = serverKey.toPublicPem("utf8").split("\n"); + var publicKeyStr = ""; + for (i=1;i<publicKeyStrArr.length - 2;i++) { + publicKeyStr += publicKeyStrArr[i] + } + client.publicKey = new Buffer(publicKeyStr,'base64'); + hash = crypto.createHash("sha1"); + hash.update(serverId); + client.write(0xFD, { + serverId: serverId, + publicKey: client.publicKey, + verifyToken: client.verifyToken + }); + } else { + logInClient(); + } + } + + function onEncryptionKeyResponse(packet) { + var success = client.verifyToken.toString("hex") === serverKey.decrypt(packet.verifyToken, undefined, undefined, ursa.RSA_PKCS1_PADDING).toString("hex"); + if (success) { + var sharedSecret = serverKey.decrypt(packet.sharedSecret, undefined, undefined, ursa.RSA_PKCS1_PADDING); + client.cipher = crypto.createCipheriv('aes-128-cfb8', sharedSecret, sharedSecret); + client.decipher = crypto.createDecipheriv('aes-128-cfb8', sharedSecret, sharedSecret); + hash.update(sharedSecret); + hash.update(client.publicKey); + var digest = mcHexDigest(hash); + if (onlineMode) { + var request = superagent.get("http://session.minecraft.net/game/checkserver.jsp"); + request.query({ + user: client.username, + serverId: digest + }); + request.end(function (err,resp){ + if (err) { + client.end('Error :', err); + return; + } + if (resp.text !== "YES") { + client.end('FailedToVerifyUsername'); + return; + } + }); + } + client.write(0xFC, { + sharedSecret: new Buffer(0), + verifyToken: new Buffer(0) + }); + client.encryptionEnabled = true; + loginClient(); + } else { + client.end('DidNotEncryptVerifyTokenProperly'); + } + } + + function loginClient() { + loggedIn = true; startKeepAlive(); clearTimeout(loginKickTimer);