successfully log in to minecraft server

next step is to add more supported packets
This commit is contained in:
Andrew Kelley 2013-01-01 04:14:38 -05:00
parent 3bc30a97b5
commit fd43515c42
4 changed files with 166 additions and 17 deletions

View File

@ -5,3 +5,29 @@ level library to write bots.
Hopefully eventually we can merge Hopefully eventually we can merge
[mineflayer](https://github.com/superjoe30/mineflayer) with this project. [mineflayer](https://github.com/superjoe30/mineflayer) with this project.
## Try it out so far
```
$ MC_EMAIL=you@example.com MC_PASSWORD=your_pass node test.js
logging in to minecraft.net
logged in as user_name
connect
enc key request
write enc key response
confirmation enc key response
writing 205 packet with encryption
login request { id: 1,
entityId: 839,
levelType: 'default',
gameMode: 0,
dimension: 0,
difficulty: 1,
_notUsed1: 0,
maxPlayers: 20 }
assert.js:102
throw new assert.AssertionError({
^
AssertionError: Unrecognized packetId: 6
```

View File

@ -46,6 +46,10 @@ Parser.prototype.connect = function(port, host) {
}); });
}; };
Parser.prototype.end = function() {
this.client.end();
};
Parser.prototype.writePacket = function(packetId, params) { Parser.prototype.writePacket = function(packetId, params) {
var buffer = createPacketBuffer(packetId, params); var buffer = createPacketBuffer(packetId, params);
var out = this.encryptionEnabled ? new Buffer(this.cipher.update(buffer), 'binary') : buffer; var out = this.encryptionEnabled ? new Buffer(this.cipher.update(buffer), 'binary') : buffer;
@ -64,6 +68,8 @@ var readers = {
'string': readString, 'string': readString,
'byteArray': readByteArray, 'byteArray': readByteArray,
'short': readShort, 'short': readShort,
'int': readInt,
'byte': readByte,
}; };
function readString (buffer, offset) { function readString (buffer, offset) {
@ -74,7 +80,7 @@ function readString (buffer, offset) {
var strLen = results.value; var strLen = results.value;
var strEnd = strBegin + strLen * 2; var strEnd = strBegin + strLen * 2;
if (strEnd > buffer.length) return null; if (strEnd > buffer.length) return null;
var str = fromUcs2.convert(buffer.slice(strBegin, strEnd)).toString(); var str = fromUcs2.convert(buffer.slice(strBegin, strEnd)).toString('utf8');
return { return {
value: str, value: str,
@ -107,6 +113,24 @@ function readShort(buffer, offset) {
}; };
} }
function readInt(buffer, offset) {
if (offset + 4 > buffer.length) return null;
var value = buffer.readInt32BE(offset);
return {
value: value,
size: 4,
};
}
function readByte(buffer, offset) {
if (offset + 1 > buffer.length) return null;
var value = buffer.readInt8(offset);
return {
value: value,
size: 1,
};
}
function StringWriter(value) { function StringWriter(value) {
this.value = value; this.value = value;
this.encoded = toUcs2.convert(value); this.encoded = toUcs2.convert(value);

View File

@ -35,7 +35,7 @@
"type": "byte" "type": "byte"
}, },
{ {
"name": "userName", "name": "username",
"type": "string" "type": "string"
}, },
{ {

129
test.js
View File

@ -2,15 +2,23 @@ var Parser = require('./lib/parser')
, ursa = require('ursa') , ursa = require('ursa')
, crypto = require('crypto') , crypto = require('crypto')
, assert = require('assert') , assert = require('assert')
, superagent = require('superagent')
var input = {
email: process.env.MC_EMAIL,
password: process.env.MC_PASSWORD,
serverHost: 'localhost',
serverPort: 25565,
};
var parser = new Parser(); var parser = new Parser();
parser.on('connect', function() { parser.on('connect', function() {
console.info("connect"); console.info("connect");
parser.writePacket(Parser.HANDSHAKE, { parser.writePacket(Parser.HANDSHAKE, {
protocolVersion: 51, protocolVersion: 51,
userName: 'superjoe30', username: loginSession.username,
serverHost: 'localhost', serverHost: input.serverHost,
serverPort: 25565, serverPort: input.serverPort,
}); });
}); });
parser.on('packet', function(packet) { parser.on('packet', function(packet) {
@ -27,7 +35,44 @@ parser.on('error', function(err) {
parser.on('end', function() { parser.on('end', function() {
console.info("disconnect"); console.info("disconnect");
}); });
parser.connect(25565, 'localhost');
var loginSession = null;
getLoginSession(function() {
parser.connect(input.serverPort, input.serverHost);
});
function getLoginSession(cb) {
console.log("logging in to minecraft.net");
var req = superagent.post("https://login.minecraft.net");
req.type('form');
req.send({
user: input.email,
password: input.password,
version: 13,
});
req.end(function(err, resp) {
if (err) {
cb(err);
} else if (! resp.ok) {
cb(new Error("login.minecraft.net status " + resp.status + ": " + resp.text));
} else {
var values = resp.text.split(':');
var session = {
currentGameVersion: values[0],
username: values[2],
id: values[3],
uid: values[4],
};
if (session.id) {
loginSession = session;
console.info("logged in as", session.username);
cb();
} else {
cb(new Error("login.minecraft.net says " + session.currentGameVersion));
}
}
});
}
var packetHandlers = { var packetHandlers = {
0xFC: onEncryptionKeyResponse, 0xFC: onEncryptionKeyResponse,
@ -46,20 +91,46 @@ function onLoginRequest(packet) {
function onEncryptionKeyRequest(packet) { function onEncryptionKeyRequest(packet) {
console.log("enc key request"); console.log("enc key request");
var hash = crypto.createHash('sha1');
hash.update(packet.serverId);
crypto.randomBytes(16, function (err, sharedSecret) { crypto.randomBytes(16, function (err, sharedSecret) {
assert.ifError(err); assert.ifError(err);
var pubKey = mcPubKeyToURsa(packet.publicKey); hash.update(sharedSecret);
var encryptedSharedSecret = pubKey.encrypt(sharedSecret, 'binary', 'base64', ursa.RSA_PKCS1_PADDING); hash.update(packet.publicKey);
var encryptedSharedSecretBuffer = new Buffer(encryptedSharedSecret, 'base64'); var digest = mcHexDigest(hash);
var encryptedVerifyToken = pubKey.encrypt(packet.verifyToken, 'binary', 'base64', ursa.RSA_PKCS1_PADDING); var request = superagent.get("http://session.minecraft.net/game/joinserver.jsp");
var encryptedVerifyTokenBuffer = new Buffer(encryptedVerifyToken, 'base64');
parser.cipher = crypto.createCipheriv('aes-128-cfb8', sharedSecret, sharedSecret); request.query({
parser.decipher = crypto.createDecipheriv('aes-128-cfb8', sharedSecret, sharedSecret); user: loginSession.username,
console.log("write enc key response"); sessionId: loginSession.id,
parser.writePacket(Parser.ENCRYPTION_KEY_RESPONSE, { serverId: digest,
sharedSecret: encryptedSharedSecretBuffer,
verifyToken: encryptedVerifyTokenBuffer,
}); });
request.end(function(err, resp) {
if (err) {
console.error("session.minecraft.net not available");
// TODO emit error
} else if (! resp.ok) {
console.error("session.minecraft.net returned error:", resp.status, resp.text);
// TODO emit error
} else {
sendEncryptionKeyResponse();
}
});
function sendEncryptionKeyResponse() {
var pubKey = mcPubKeyToURsa(packet.publicKey);
var encryptedSharedSecret = pubKey.encrypt(sharedSecret, 'binary', 'base64', ursa.RSA_PKCS1_PADDING);
var encryptedSharedSecretBuffer = new Buffer(encryptedSharedSecret, 'base64');
var encryptedVerifyToken = pubKey.encrypt(packet.verifyToken, 'binary', 'base64', ursa.RSA_PKCS1_PADDING);
var encryptedVerifyTokenBuffer = new Buffer(encryptedVerifyToken, 'base64');
parser.cipher = crypto.createCipheriv('aes-128-cfb8', sharedSecret, sharedSecret);
parser.decipher = crypto.createDecipheriv('aes-128-cfb8', sharedSecret, sharedSecret);
console.log("write enc key response");
parser.writePacket(Parser.ENCRYPTION_KEY_RESPONSE, {
sharedSecret: encryptedSharedSecretBuffer,
verifyToken: encryptedVerifyTokenBuffer,
});
}
}); });
} }
@ -82,3 +153,31 @@ function mcPubKeyToURsa(mcPubKeyBuffer) {
pem += "-----END PUBLIC KEY-----\n"; pem += "-----END PUBLIC KEY-----\n";
return ursa.createPublicKey(pem, 'utf8'); return ursa.createPublicKey(pem, 'utf8');
} }
function mcHexDigest(hash) {
var buffer = new Buffer(hash.digest(), 'binary');
// check for negative hashes
var negative = buffer.readInt8(0) < 0;
if (negative) performTwosCompliment(buffer);
var digest = buffer.toString('hex');
// trim leading zeroes
digest = digest.replace(/^0+/g, '');
if (negative) digest = '-' + digest;
return digest;
function performTwosCompliment(buffer) {
var carry = true;
var i, newByte, value;
for (i = buffer.length - 1; i >= 0; --i) {
value = buffer.readUInt8(i);
newByte = ~value & 0xff;
if (carry) {
carry = newByte === 0xff;
buffer.writeUInt8(newByte + 1, i);
} else {
buffer.writeUInt8(newByte, i);
}
}
}
}