diff --git a/src/auth/jwt.js b/src/auth/chains.js similarity index 92% rename from src/auth/jwt.js rename to src/auth/chains.js index 155f7fb..5208b9f 100644 --- a/src/auth/jwt.js +++ b/src/auth/chains.js @@ -80,7 +80,18 @@ function decodeLoginJWT(authTokens, skinTokens) { return { key, userData: data, skinData } } -function test() { +function encodeLoginJWT(localChain, mojangChain) { + const chains = [] + chains.push(localChain) + for (const chain of mojangChain) { + chains.push(chain) + } + return chains +} + +module.exports = { encodeLoginJWT, decodeLoginJWT } + +function testServer() { const loginPacket = require('./login.json') // console.log(loginPacket) @@ -95,10 +106,7 @@ function test() { } console.log('Authed') - // console.log(loginPacket) } -module.exports = { decodeLoginJWT } - -// test() \ No newline at end of file +// testServer() \ No newline at end of file diff --git a/src/auth/encryption.js b/src/auth/encryption.js index a487f3e..f32bc69 100644 --- a/src/auth/encryption.js +++ b/src/auth/encryption.js @@ -4,15 +4,19 @@ const { Ber } = require('asn1') const ec_pem = require('ec-pem') const SALT = '🧂' +const curve = 'secp384r1' function Encrypt(client, options) { + client.ecdhKeyPair = crypto.createECDH(curve) + client.ecdhKeyPair.generateKeys() + + createClientChain(client) + function startClientboundEncryption(publicKey) { console.warn('[encrypt] Pub key base64: ', publicKey) const pubKeyBuf = readX509PublicKey(publicKey.key) - const curve = 'secp384r1' - const alice = crypto.createECDH(curve) - alice.generateKeys() + const alice = client.ecdhKeyPair const alicePEM = ec_pem(alice, curve) // https://github.com/nodejs/node/issues/15116#issuecomment-384790125 const alicePEMPrivate = alicePEM.encodePrivateKey() // Shared secret from bob's public key + our private key @@ -47,9 +51,27 @@ function Encrypt(client, options) { client.startEncryption(initial) } + function startServerboundEncryption() { + + } + client.on('server.client_handshake', startClientboundEncryption) } +function createClientChain(client) { + const alice = client.ecdhKeyPair + const alicePEM = ec_pem(alice, curve) // https://github.com/nodejs/node/issues/15116#issuecomment-384790125 + const alicePEMPrivate = alicePEM.encodePrivateKey() + const x509 = writeX509PublicKey(alice.getPublicKey()) + + const token = JWT.sign({ + salt: toBase64(SALT), + signedToken: alice.getPublicKey('base64') + }, alicePEMPrivate, { algorithm: 'ES384', header: { x5u: x509 } }) + + client.clientChain = token +} + function toBase64(string) { return Buffer.from(string).toString('base64') } diff --git a/src/connection.js b/src/connection.js new file mode 100644 index 0000000..9830b57 --- /dev/null +++ b/src/connection.js @@ -0,0 +1,107 @@ +const BinaryStream = require('@jsprismarine/jsbinaryutils').default +const BatchPacket = require('./BatchPacket') +const cipher = require('./transforms/encryption') +const { EventEmitter } = require('events') +const EncapsulatedPacket = require('@jsprismarine/raknet/protocol/encapsulated_packet') + + +class Connection extends EventEmitter { + startEncryption(iv) { + this.encryptionEnabled = true + + this.decrypt = cipher.createDecryptor(this, iv) + this.encrypt = cipher.createEncryptor(this, iv) + } + + write(name, params) { // TODO: Batch + console.log('Need to encode', name, params) + const batch = new BatchPacket() + const packet = this.server.serializer.createPacketBuffer({ name, params }) + batch.addEncodedPacket(packet) + + if (this.encryptionEnabled) { + this.sendEncryptedBatch(batch) + } else { + this.sendDecryptedBatch(batch) + } + } + + writeRaw(name, buffer) { // skip protodef serializaion + // temporary hard coded stuff + const batch = new BatchPacket() + if (name == 'biome_definition_list') { + // so we can send nbt straight from file without parsing + const stream = new BinaryStream() + stream.writeUnsignedVarInt(0x7a) + stream.append(buffer) + batch.addEncodedPacket(stream.getBuffer()) + // console.log('----- SENDING BIOME DEFINITIONS') + } + + if (this.encryptionEnabled) { + this.sendEncryptedBatch(batch) + } else { + this.sendDecryptedBatch(batch) + } + } + + sendDecryptedBatch(batch) { + const buf = batch.encode() + // send to raknet + const sendPacket = new EncapsulatedPacket(); + sendPacket.reliability = 0; + sendPacket.buffer = buf + + this.connection.addEncapsulatedToQueue(sendPacket) + this.connection.sendQueue() + } + + sendEncryptedBatch(batch) { + const buf = batch.stream.getBuffer() + console.log('Sending encrypted batch', batch) + this.encrypt(buf) + } + + // These are callbacks called from encryption.js + onEncryptedPacket = (buf) => { + console.log('ENC BUF', buf) + const packet = Buffer.concat([Buffer.from([0xfe]), buf]) // add header + const sendPacket = new EncapsulatedPacket(); + sendPacket.reliability = 0 + sendPacket.buffer = packet + console.log('Sending wrapped encrypted batch', packet) + this.connection.addEncapsulatedToQueue(sendPacket) + } + + onDecryptedPacket = (buf) => { + console.log('Decrypted', buf) + + const stream = new BinaryStream(buf) + const packets = BatchPacket.getPackets(stream) + + for (const packet of packets) { + this.readPacket(packet) + } + } + + handle(buffer) { // handle encapsulated + if (buffer[0] == 0xfe) { // wrapper + + if (this.encryptionEnabled) { + // console.log('READING ENCRYPTED PACKET', buffer) + this.decrypt(buffer.slice(1)) + } else { + const stream = new BinaryStream(buffer) + const batch = new BatchPacket(stream) + batch.decode() + const packets = batch.getPackets() + console.log('Reading ', packets.length, 'packets') + for (var packet of packets) { + this.readPacket(packet) + } + } + } + } +} + +module.exports = { Connection } \ No newline at end of file diff --git a/src/server.js b/src/server.js index 636f104..af25535 100644 --- a/src/server.js +++ b/src/server.js @@ -1,14 +1,9 @@ -const BinaryStream = require('@jsprismarine/jsbinaryutils').default const Listener = require('@jsprismarine/raknet/listener') const { ProtoDef, Parser, Serializer } = require('protodef') -const BatchPacket = require('./BatchPacket') const { EventEmitter } = require('events') -const cipher = require('./transforms/encryption') const { Encrypt } = require('./auth/encryption') - -const { decodeLoginJWT } = require('./auth/jwt') -const EncapsulatedPacket = require('@jsprismarine/raknet/protocol/encapsulated_packet') - +const { decodeLoginJWT } = require('./auth/chains') +const { Connection } = require('./connection') var protocol = require('../data/newproto.json').types; @@ -41,7 +36,7 @@ const PLAY_STATUS = { 'LoginFailedServerFull': 7 } -class Player extends EventEmitter { +class Player extends Connection { constructor(server, connection, options) { super() this.server = server @@ -100,84 +95,6 @@ class Player extends EventEmitter { this.emit('join') } - startEncryption(iv) { - this.encryptionEnabled = true - - this.decrypt = cipher.createDecryptor(this, iv) - this.encrypt = cipher.createEncryptor(this, iv) - } - - write(name, params) { // TODO: Batch - console.log('Need to encode', name, params) - const batch = new BatchPacket() - const packet = this.server.serializer.createPacketBuffer({ name, params }) - batch.addEncodedPacket(packet) - - if (this.encryptionEnabled) { - this.sendEncryptedBatch(batch) - } else { - this.sendDecryptedBatch(batch) - } - } - - writeRaw(name, buffer) { // skip protodef serializaion - // temporary hard coded stuff - const batch = new BatchPacket() - if (name == 'biome_definition_list') { - // so we can send nbt straight from file without parsing - const stream = new BinaryStream() - stream.writeUnsignedVarInt(0x7a) - stream.append(buffer) - batch.addEncodedPacket(stream.getBuffer()) - // console.log('----- SENDING BIOME DEFINITIONS') - } - - if (this.encryptionEnabled) { - this.sendEncryptedBatch(batch) - } else { - this.sendDecryptedBatch(batch) - } - } - - sendDecryptedBatch(batch) { - const buf = batch.encode() - // send to raknet - const sendPacket = new EncapsulatedPacket(); - sendPacket.reliability = 0; - sendPacket.buffer = buf - - this.connection.addEncapsulatedToQueue(sendPacket) - this.connection.sendQueue() - } - - sendEncryptedBatch(batch) { - const buf = batch.stream.getBuffer() - console.log('Sending encrypted batch', batch) - this.encrypt(buf) - } - - // These are callbacks called from encryption.js - onEncryptedPacket = (buf) => { - console.log('ENC BUF', buf) - const packet = Buffer.concat([Buffer.from([0xfe]), buf]) // add header - const sendPacket = new EncapsulatedPacket(); - sendPacket.reliability = 0 - sendPacket.buffer = packet - console.log('Sending wrapped encrypted batch', packet) - this.connection.addEncapsulatedToQueue(sendPacket) - } - - onDecryptedPacket = (buf) => { - console.log('Decrypted', buf) - - const stream = new BinaryStream(buf) - const packets = BatchPacket.getPackets(stream) - - for (const packet of packets) { - this.readPacket(packet) - } - } - readPacket(packet) { console.log('packet', packet) const des = this.server.deserializer.parsePacketBuffer(packet) @@ -193,26 +110,6 @@ class Player extends EventEmitter { console.log('ignoring, unhandled') } this.emit(des.data.name, des.data.params) - - } - - handle(buffer) { // handle encapsulated - if (buffer[0] == 0xfe) { // wrapper - - if (this.encryptionEnabled) { - // console.log('READING ENCRYPTED PACKET', buffer) - this.decrypt(buffer.slice(1)) - } else { - const stream = new BinaryStream(buffer) - const batch = new BatchPacket(stream) - batch.decode() - const packets = batch.getPackets() - console.log('Reading ', packets.length, 'packets') - for (var packet of packets) { - this.readPacket(packet) - } - } - } } }