diff --git a/data/new/proto.yml b/data/latest/proto.yml similarity index 96% rename from data/new/proto.yml rename to data/latest/proto.yml index f296075..6253c5a 100644 --- a/data/new/proto.yml +++ b/data/latest/proto.yml @@ -945,8 +945,7 @@ packet_inventory_slot: slot: varint # NewItem is the item to be put in the slot at Slot. It will overwrite any item that may currently # be present in that slot. - uniqueid: zigzag32 - item: Item + item: ItemStack # ContainerSetData is sent by the server to update specific data of a single container, meaning a block such # as a furnace or a brewing stand. This data is usually used by the client to display certain features @@ -1325,97 +1324,6 @@ packet_available_commands: 43: raw_text 46: json 53: command - # 15: unknown15 - # 16: unknown16 - # 125: unknown125 - # 126: unknown126 - # 22: unknown22 - # 10: unknown10 - # 39: unknown39 - # 18: unknown18 - # 20: unknown20 - # 19: unknown19 - # 7: unknown7 - # 23: unknown23 - # 24: unknown24 - # 13: unknown13 - # 25: unknown25 - # 40: unknown40 - # 56: unknown56 - # 26: unknown26 - # 27: unknown27 - # 28: unknown28 - # 31: unknown31 - # 30: unknown30 - # 0: unknown0 - # 32: unknown32 - # 49: unknown49 - # 12: unknown12 - # 35: unknown35 - # 36: unknown36 - # 38: unknown38 - # 44: unknown44 - # 47: unknown47 - # 45: unknown45 - # 48: unknown48 - # 55: unknown55 - # 54: unknown54 - # 50: unknown50 - # 51: unknown51 - # 52: unknown52 - # 9: unknown9 - # 123: unknown123 - # 57: unknown57 - # 58: unknown58 - # 59: unknown59 - # 60: unknown60 - # 61: unknown61 - # 63: unknown63 - # 75: unknown75 - # 64: unknown64 - # 67: unknown67 - # 66: unknown66 - # 73: unknown73 - # 72: unknown72 - # 74: unknown74 - # 62: unknown62 - # 68: unknown68 - # 69: unknown69 - # 65: unknown65 - # 70: unknown70 - # 71: unknown71 - # 76: unknown76 - # 77: unknown77 - # 79: unknown79 - # 78: unknown78 - # 80: unknown80 - # 81: unknown81 - # 82: unknown82 - # 83: unknown83 - # 84: unknown84 - # 87: unknown87 - # 88: unknown88 - # 92: unknown92 - # 89: unknown89 - # 90: unknown90 - # 91: unknown91 - # 93: unknown93 - # 95: unknown95 - # 94: unknown94 - # 98: unknown98 - # 96: unknown96 - # 97: unknown97 - # 99: unknown99 - # 100: unknown100 - # 101: unknown101 - # 102: unknown102 - # 103: unknown103 - # 104: unknown104 - # 105: unknown105 - # 106: unknown106 - # 107: unknown107 - # 108: unknown108 - # 124: unknown124 # In MC, this + prior field are combined to one 32bit bitfield enum_type: lu16 => 0x10: valid diff --git a/data/new/types.yaml b/data/latest/types.yaml similarity index 98% rename from data/new/types.yaml rename to data/latest/types.yaml index adf7b3b..6a8cecc 100644 --- a/data/new/types.yaml +++ b/data/latest/types.yaml @@ -408,10 +408,18 @@ Transaction: # mainly for purposes such as spawning eating particles at that position. head_pos: vec3f -ItemStacks: []varint +# An "ItemStack" here represents an Item instance. You can think about it like a pointer +# to an item class. The data for the class gets updated with the data in the `item` field +ItemStack: + # StackNetworkID is the network ID of the item stack. If the stack is empty, 0 is always written for this + # field. If not, the field should be set to 1 if the server authoritative inventories are disabled in the + # StartGame packet, or to a unique stack ID if it is enabled. runtime_id: zigzag32 + # Stack is the actual item stack of the item instance. item: Item +ItemStacks: ItemStack[]varint + RecipeIngredient: network_id: zigzag32 _: network_id? diff --git a/data/provider.js b/data/provider.js index 437d53a..098564a 100644 --- a/data/provider.js +++ b/data/provider.js @@ -14,7 +14,7 @@ function loadVersions () { files = getFiles(join(__dirname, '/', version)) } catch {} for (const file of files) { - const rfile = file.replace(join(__dirname, '/', version), '') + const rfile = file.replace(join(__dirname, '/', version) + '/', '') fileMap[rfile] ??= [] fileMap[rfile].push([Versions[version], file]) fileMap[rfile].sort().reverse() @@ -42,5 +42,3 @@ module.exports = (protocolVersion) => { } loadVersions() -// console.log('file map', fileMap) -// module.exports(Versions['1.16.210']).open('creativeitems.json') diff --git a/src/auth/chains.js b/src/auth/chains.js deleted file mode 100644 index 2c350d8..0000000 --- a/src/auth/chains.js +++ /dev/null @@ -1,110 +0,0 @@ -const JWT = require('jsonwebtoken') -const constants = require('./constants') - -// Refer to the docs: -// https://web.archive.org/web/20180917171505if_/https://confluence.yawk.at/display/PEPROTOCOL/Game+Packets#GamePackets-Login - -function mcPubKeyToPem (mcPubKeyBuffer) { - console.log(mcPubKeyBuffer) - if (mcPubKeyBuffer[0] === '-') return mcPubKeyBuffer - let pem = '-----BEGIN PUBLIC KEY-----\n' - let base64PubKey = mcPubKeyBuffer.toString('base64') - const maxLineLength = 65 - while (base64PubKey.length > 0) { - pem += base64PubKey.substring(0, maxLineLength) + '\n' - base64PubKey = base64PubKey.substring(maxLineLength) - } - pem += '-----END PUBLIC KEY-----\n' - return pem -} - -function getX5U (token) { - const [header] = token.split('.') - const hdec = Buffer.from(header, 'base64').toString('utf-8') - const hjson = JSON.parse(hdec) - return hjson.x5u -} - -function verifyAuth (chain) { - let data = {} - - // There are three JWT tokens sent to us, one signed by the client - // one signed by Mojang with the Mojang token we have and another one - // from Xbox with addition user profile data - // We verify that at least one of the tokens in the chain has been properly - // signed by Mojang by checking the x509 public key in the JWT headers - // let didVerify = false - - let pubKey = mcPubKeyToPem(getX5U(chain[0])) // the first one is client signed, allow it - let finalKey = null - console.log(pubKey) - for (const token of chain) { - // const decoded = jwt.decode(token, pubKey, 'ES384') - // console.log('Decoding...', token) - const decoded = JWT.verify(token, pubKey, { algorithms: 'ES384' }) - // console.log('Decoded...') - console.log('Decoded', decoded) - - // Check if signed by Mojang key - const x5u = getX5U(token) - if (x5u === constants.PUBLIC_KEY && !data.extraData?.XUID) { - // didVerify = true - console.log('verified with mojang key!', x5u) - } - - // TODO: Handle `didVerify` = false - - pubKey = decoded.identityPublicKey ? mcPubKeyToPem(decoded.identityPublicKey) : x5u - finalKey = decoded.identityPublicKey || finalKey // non pem - data = { ...data, ...decoded } - } - // console.log('Result', data) - - return { key: finalKey, data } -} - -function verifySkin (publicKey, token) { - // console.log('token', token) - const pubKey = mcPubKeyToPem(publicKey) - - const decoded = JWT.verify(token, pubKey, { algorithms: 'ES384' }) - - return decoded -} - -function decodeLoginJWT (authTokens, skinTokens) { - const { key, data } = verifyAuth(authTokens) - const skinData = verifySkin(key, skinTokens) - return { key, userData: data, skinData } -} - -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) -// const authChains = JSON.parse(loginPacket.data.chain) -// const skinChain = loginPacket.data.clientData - -// try { -// var { data, chain } = decodeLoginJWT(authChains.chain, skinChain) -// } catch (e) { -// console.error(e) -// throw new Error('Failed to verify user') -// } - -// console.log('Authed') -// // console.log(loginPacket) -// } - -// // testServer() diff --git a/src/auth/encryption.js b/src/auth/encryption.js index 56afa1d..99d8d34 100644 --- a/src/auth/encryption.js +++ b/src/auth/encryption.js @@ -1,28 +1,25 @@ +const { Ber } = require('asn1') const JWT = require('jsonwebtoken') const crypto = require('crypto') -const { Ber } = require('asn1') const ecPem = require('ec-pem') -const fs = require('fs') -const DataProvider = require('../../data/provider') +const debug = require('debug')('minecraft-protocol') const SALT = '🧂' const curve = 'secp384r1' function Encrypt (client, server, options) { - const skinGeom = fs.readFileSync(DataProvider(options.protocolVersion).getPath('skin_geom.txt'), 'utf-8') - client.ecdhKeyPair = crypto.createECDH(curve) client.ecdhKeyPair.generateKeys() client.clientX509 = writeX509PublicKey(client.ecdhKeyPair.getPublicKey()) function startClientboundEncryption (publicKey) { - console.warn('[encrypt] Pub key base64: ', publicKey) + debug('[encrypt] Client pub key base64: ', publicKey) const pubKeyBuf = readX509PublicKey(publicKey.key) const alice = client.ecdhKeyPair const alicePEM = ecPem(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 + // Shared secret from the client's public key + our private key client.sharedSecret = alice.computeSecret(pubKeyBuf) // Secret hash we use for packet encryption: @@ -34,10 +31,10 @@ function Encrypt (client, server, options) { const secretHash = crypto.createHash('sha256') secretHash.update(SALT) secretHash.update(client.sharedSecret) - console.log('[encrypt] Shared secret', client.sharedSecret) + // console.log('[encrypt] Shared secret', client.sharedSecret) client.secretKeyBytes = secretHash.digest() - console.log('[encrypt] Shared hash', client.secretKeyBytes) + // console.log('[encrypt] Shared hash', client.secretKeyBytes) const x509 = writeX509PublicKey(alice.getPublicKey()) const token = JWT.sign({ salt: toBase64(SALT), @@ -56,7 +53,7 @@ function Encrypt (client, server, options) { } function startServerboundEncryption (token) { - console.warn('[encrypt] Starting serverbound encryption', token) + debug('[encrypt] Starting serverbound encryption', token) const jwt = token?.token if (!jwt) { // TODO: allow connecting to servers without encryption @@ -69,7 +66,7 @@ function Encrypt (client, server, options) { const body = JSON.parse(String(payload)) const serverPublicKey = readX509PublicKey(head.x5u) client.sharedSecret = alice.computeSecret(serverPublicKey) - console.log('[encrypt] Shared secret', client.sharedSecret) + // console.log('[encrypt] Shared secret', client.sharedSecret) const salt = Buffer.from(body.salt, 'base64') @@ -78,7 +75,7 @@ function Encrypt (client, server, options) { secretHash.update(client.sharedSecret) client.secretKeyBytes = secretHash.digest() - console.log('[encrypt] Shared hash', client.secretKeyBytes) + // console.log('[encrypt] Shared hash', client.secretKeyBytes) const initial = client.secretKeyBytes.slice(0, 16) client.startEncryption(initial) @@ -89,70 +86,6 @@ function Encrypt (client, server, options) { client.on('server.client_handshake', startClientboundEncryption) client.on('client.server_handshake', startServerboundEncryption) - - client.createClientChain = (mojangKey) => { - mojangKey = mojangKey || require('./constants').PUBLIC_KEY - const alice = client.ecdhKeyPair - const alicePEM = ecPem(alice, curve) // https://github.com/nodejs/node/issues/15116#issuecomment-384790125 - const alicePEMPrivate = alicePEM.encodePrivateKey() - - const token = JWT.sign({ - identityPublicKey: mojangKey, - certificateAuthority: true - }, alicePEMPrivate, { algorithm: 'ES384', header: { x5u: client.clientX509 } }) - - client.clientIdentityChain = token - client.createClientUserChain(alicePEMPrivate) - } - - client.createClientUserChain = (privateKey) => { - let payload = { - ServerAddress: options.hostname, - ThirdPartyName: client.profile.name, - DeviceOS: client.session?.deviceOS || 1, - GameVersion: options.version || '1.16.201', - ClientRandomId: Date.now(), // TODO make biggeer - DeviceId: '2099de18-429a-465a-a49b-fc4710a17bb3', // TODO random - LanguageCode: 'en_GB', // TODO locale - AnimatedImageData: [], - PersonaPieces: [], - PieceTintColours: [], - SelfSignedId: '78eb38a6-950e-3ab9-b2cf-dd849e343701', - SkinId: '5eb65f73-af11-448e-82aa-1b7b165316ad.persona-e199672a8c1a87e0-0', - SkinData: 'AAAAAA==', - SkinResourcePatch: 'ewogICAiZ2VvbWV0cnkiIDogewogICAgICAiYW5pbWF0ZWRfMTI4eDEyOCIgOiAiZ2VvbWV0cnkuYW5pbWF0ZWRfMTI4eDEyOF9wZXJzb25hLWUxOTk2NzJhOGMxYTg3ZTAtMCIsCiAgICAgICJhbmltYXRlZF9mYWNlIiA6ICJnZW9tZXRyeS5hbmltYXRlZF9mYWNlX3BlcnNvbmEtZTE5OTY3MmE4YzFhODdlMC0wIiwKICAgICAgImRlZmF1bHQiIDogImdlb21ldHJ5LnBlcnNvbmFfZTE5OTY3MmE4YzFhODdlMC0wIgogICB9Cn0K', - SkinGeometryData: skinGeom, - SkinImageHeight: 1, - SkinImageWidth: 1, - ArmSize: 'wide', - CapeData: '', - CapeId: '', - CapeImageHeight: 0, - CapeImageWidth: 0, - CapeOnClassicSkin: false, - PlatformOfflineId: '', - PlatformOnlineId: '', // chat - // a bunch of meaningless junk - CurrentInputMode: 1, - DefaultInputMode: 1, - DeviceModel: '', - GuiScale: -1, - UIProfile: 0, - TenantId: '', - PremiumSkin: false, - PersonaSkin: false, - PieceTintColors: [], - SkinAnimationData: '', - ThirdPartyNameOnly: false, - SkinColor: '#ffffcd96' - } - payload = require('./logPack.json') - const customPayload = options.userData || {} - payload = { ...payload, ...customPayload } - - client.clientUserChain = JWT.sign(payload, privateKey, - { algorithm: 'ES384', header: { x5u: client.clientX509 } }) - } } function toBase64 (string) { diff --git a/src/auth/login.js b/src/auth/login.js new file mode 100644 index 0000000..e414c5d --- /dev/null +++ b/src/auth/login.js @@ -0,0 +1,73 @@ +const fs = require('fs') +const JWT = require('jsonwebtoken') +const DataProvider = require('../../data/provider') +const ecPem = require('ec-pem') +const curve = 'secp384r1' + +module.exports = (client, server, options) => { + const skinGeom = fs.readFileSync(DataProvider(options.protocolVersion).getPath('skin_geom.txt'), 'utf-8') + + client.createClientChain = (mojangKey) => { + mojangKey = mojangKey || require('./constants').PUBLIC_KEY + const alice = client.ecdhKeyPair + const alicePEM = ecPem(alice, curve) // https://github.com/nodejs/node/issues/15116#issuecomment-384790125 + const alicePEMPrivate = alicePEM.encodePrivateKey() + + const token = JWT.sign({ + identityPublicKey: mojangKey, + certificateAuthority: true + }, alicePEMPrivate, { algorithm: 'ES384', header: { x5u: client.clientX509 } }) + + client.clientIdentityChain = token + client.createClientUserChain(alicePEMPrivate) + } + + client.createClientUserChain = (privateKey) => { + let payload = { + ServerAddress: options.hostname, + ThirdPartyName: client.profile.name, + DeviceOS: client.session?.deviceOS || 1, + GameVersion: options.version || '1.16.201', + ClientRandomId: Date.now(), // TODO make biggeer + DeviceId: '2099de18-429a-465a-a49b-fc4710a17bb3', // TODO random + LanguageCode: 'en_GB', // TODO locale + AnimatedImageData: [], + PersonaPieces: [], + PieceTintColours: [], + SelfSignedId: '78eb38a6-950e-3ab9-b2cf-dd849e343701', + SkinId: '5eb65f73-af11-448e-82aa-1b7b165316ad.persona-e199672a8c1a87e0-0', + SkinData: 'AAAAAA==', + SkinResourcePatch: 'ewogICAiZ2VvbWV0cnkiIDogewogICAgICAiYW5pbWF0ZWRfMTI4eDEyOCIgOiAiZ2VvbWV0cnkuYW5pbWF0ZWRfMTI4eDEyOF9wZXJzb25hLWUxOTk2NzJhOGMxYTg3ZTAtMCIsCiAgICAgICJhbmltYXRlZF9mYWNlIiA6ICJnZW9tZXRyeS5hbmltYXRlZF9mYWNlX3BlcnNvbmEtZTE5OTY3MmE4YzFhODdlMC0wIiwKICAgICAgImRlZmF1bHQiIDogImdlb21ldHJ5LnBlcnNvbmFfZTE5OTY3MmE4YzFhODdlMC0wIgogICB9Cn0K', + SkinGeometryData: skinGeom, + SkinImageHeight: 1, + SkinImageWidth: 1, + ArmSize: 'wide', + CapeData: '', + CapeId: '', + CapeImageHeight: 0, + CapeImageWidth: 0, + CapeOnClassicSkin: false, + PlatformOfflineId: '', + PlatformOnlineId: '', // chat + // a bunch of meaningless junk + CurrentInputMode: 1, + DefaultInputMode: 1, + DeviceModel: '', + GuiScale: -1, + UIProfile: 0, + TenantId: '', + PremiumSkin: false, + PersonaSkin: false, + PieceTintColors: [], + SkinAnimationData: '', + ThirdPartyNameOnly: false, + SkinColor: '#ffffcd96' + } + payload = require('./logPack.json') + const customPayload = options.userData || {} + payload = { ...payload, ...customPayload } + + client.clientUserChain = JWT.sign(payload, privateKey, + { algorithm: 'ES384', header: { x5u: client.clientX509 } }) + } +} diff --git a/src/auth/loginVerify.js b/src/auth/loginVerify.js new file mode 100644 index 0000000..6d21e41 --- /dev/null +++ b/src/auth/loginVerify.js @@ -0,0 +1,83 @@ +const JWT = require('jsonwebtoken') +const constants = require('./constants') + +module.exports = (client, server, options) => { + // Refer to the docs: + // https://web.archive.org/web/20180917171505if_/https://confluence.yawk.at/display/PEPROTOCOL/Game+Packets#GamePackets-Login + + function verifyAuth (chain) { + let data = {} + + // There are three JWT tokens sent to us, one signed by the client + // one signed by Mojang with the Mojang token we have and another one + // from Xbox with addition user profile data + // We verify that at least one of the tokens in the chain has been properly + // signed by Mojang by checking the x509 public key in the JWT headers + // let didVerify = false + + let pubKey = mcPubKeyToPem(getX5U(chain[0])) // the first one is client signed, allow it + let finalKey = null + console.log(pubKey) + for (const token of chain) { + const decoded = JWT.verify(token, pubKey, { algorithms: 'ES384' }) + console.log('Decoded', decoded) + + // Check if signed by Mojang key + const x5u = getX5U(token) + if (x5u === constants.PUBLIC_KEY && !data.extraData?.XUID) { + // didVerify = true + console.log('verified with mojang key!', x5u) + } + + // TODO: Handle `didVerify` = false + pubKey = decoded.identityPublicKey ? mcPubKeyToPem(decoded.identityPublicKey) : x5u + finalKey = decoded.identityPublicKey || finalKey // non pem + data = { ...data, ...decoded } + } + // console.log('Result', data) + + return { key: finalKey, data } + } + + function verifySkin (publicKey, token) { + const pubKey = mcPubKeyToPem(publicKey) + const decoded = JWT.verify(token, pubKey, { algorithms: 'ES384' }) + return decoded + } + + client.decodeLoginJWT = (authTokens, skinTokens) => { + const { key, data } = verifyAuth(authTokens) + const skinData = verifySkin(key, skinTokens) + return { key, userData: data, skinData } + } + + client.encodeLoginJWT = (localChain, mojangChain) => { + const chains = [] + chains.push(localChain) + for (const chain of mojangChain) { + chains.push(chain) + } + return chains + } +} + +function getX5U (token) { + const [header] = token.split('.') + const hdec = Buffer.from(header, 'base64').toString('utf-8') + const hjson = JSON.parse(hdec) + return hjson.x5u +} + +function mcPubKeyToPem (mcPubKeyBuffer) { + console.log(mcPubKeyBuffer) + if (mcPubKeyBuffer[0] === '-') return mcPubKeyBuffer + let pem = '-----BEGIN PUBLIC KEY-----\n' + let base64PubKey = mcPubKeyBuffer.toString('base64') + const maxLineLength = 65 + while (base64PubKey.length > 0) { + pem += base64PubKey.substring(0, maxLineLength) + '\n' + base64PubKey = base64PubKey.substring(maxLineLength) + } + pem += '-----END PUBLIC KEY-----\n' + return pem +} diff --git a/src/client.js b/src/client.js index 1f859e2..5b6c960 100644 --- a/src/client.js +++ b/src/client.js @@ -1,12 +1,15 @@ -const fs = require('fs') -const debug = require('debug')('minecraft-protocol') -const auth = require('./client/auth') -const Options = require('./options') const { Connection } = require('./connection') const { createDeserializer, createSerializer } = require('./transforms/serializer') -const { Encrypt } = require('./auth/encryption') const { RakClient } = require('./Rak') const { serialize } = require('./datatypes/util') +const fs = require('fs') +const debug = require('debug')('minecraft-protocol') +const Options = require('./options') +const auth = require('./client/auth') + +const { Encrypt } = require('./auth/encryption') +const Login = require('./auth/login') +const LoginVerify = require('./auth/loginVerify') const debugging = false @@ -20,6 +23,8 @@ class Client extends Connection { this.deserializer = createDeserializer(this.options.version) Encrypt(this, null, this.options) + Login(this, null, this.options) + LoginVerify(this, null, this.options) if (options.password) { auth.authenticatePassword(this, options) diff --git a/src/client/tokens.js b/src/client/tokens.js index 9ed3a18..af82f45 100644 --- a/src/client/tokens.js +++ b/src/client/tokens.js @@ -13,12 +13,7 @@ class MsaTokenManager { this.scopes = scopes this.cacheLocation = cacheLocation || path.join(__dirname, './msa-cache.json') - try { - this.msaCache = require(this.cacheLocation) - } catch (e) { - this.msaCache = {} - fs.writeFileSync(this.cacheLocation, JSON.stringify(this.msaCache)) - } + this.reloadCache() const beforeCacheAccess = async (cacheContext) => { cacheContext.tokenCache.deserialize(await fs.promises.readFile(this.cacheLocation, 'utf-8')) @@ -42,6 +37,15 @@ class MsaTokenManager { this.msalConfig = msalConfig } + reloadCache () { + try { + this.msaCache = require(this.cacheLocation) + } catch (e) { + this.msaCache = {} + fs.writeFileSync(this.cacheLocation, JSON.stringify(this.msaCache)) + } + } + getUsers () { const accounts = this.msaCache.Account const users = [] @@ -89,6 +93,7 @@ class MsaTokenManager { return new Promise((resolve, reject) => { this.msalApp.acquireTokenByRefreshToken(refreshTokenRequest).then((response) => { debug('[msa] refreshed token', JSON.stringify(response)) + this.reloadCache() resolve(response) }).catch((error) => { debug('[msa] failed to refresh', JSON.stringify(error)) diff --git a/src/server.js b/src/server.js index 94010d0..bb98d34 100644 --- a/src/server.js +++ b/src/server.js @@ -44,7 +44,7 @@ class Server extends EventEmitter { } onEncapsulated = (buffer, address) => { - this.inLog('encapsulated', address, buffer) + // this.inLog('encapsulated', address, buffer) const client = this.clients[address] if (!client) { throw new Error(`packet from unknown inet addr: ${address}`) diff --git a/src/serverPlayer.js b/src/serverPlayer.js index 4a33aed..a699ec0 100644 --- a/src/serverPlayer.js +++ b/src/serverPlayer.js @@ -1,9 +1,10 @@ -const { Encrypt } = require('./auth/encryption') -const { decodeLoginJWT } = require('./auth/chains') const { Connection } = require('./connection') const fs = require('fs') -// const debug = require('debug')('minecraft-protocol') -const { MIN_VERSION } = require('./options') +const Options = require('./options') + +const { Encrypt } = require('./auth/encryption') +const Login = require('./auth/login') +const LoginVerify = require('./auth/loginVerify') const ClientStatus = { Authenticating: 0, @@ -19,7 +20,10 @@ class Player extends Connection { this.deserializer = server.deserializer this.connection = connection this.options = server.options - Encrypt(this, server, this.options) + + Encrypt(this, server, server.options) + Login(this, server, server.options) + LoginVerify(this, server, server.options) this.startQueue() this.status = ClientStatus.Authenticating @@ -42,7 +46,7 @@ class Player extends Connection { this.sendDisconnectStatus('failed_client') return } - } else if (clientVer < MIN_VERSION) { + } else if (clientVer < Options.MIN_VERSION) { this.sendDisconnectStatus('failed_client') return } @@ -52,7 +56,7 @@ class Player extends Connection { const skinChain = body.params.client_data try { - var { key, userData, chain } = decodeLoginJWT(authChain.chain, skinChain) // eslint-disable-line + var { key, userData, chain } = this.decodeLoginJWT(authChain.chain, skinChain) // eslint-disable-line } catch (e) { console.error(e) // TODO: disconnect user diff --git a/src/transforms/encryption.js b/src/transforms/encryption.js index 1822238..41432c1 100644 --- a/src/transforms/encryption.js +++ b/src/transforms/encryption.js @@ -88,43 +88,21 @@ function createDecryptor (client, iv) { client.receiveCounter = client.receiveCounter || 0n function verify (chunk) { - // TODO: remove the extra logic here, probably fixed with new raknet impl - // console.log('Decryptor: checking checksum', client.receiveCounter, chunk) - // client.outLog('🔵 Inflating', chunk) - // First try to zlib decompress, then see how much bytes get read - const { buffer, engine } = Zlib.inflateRawSync(chunk, { - chunkSize: 1024 * 1024 * 2, - info: true - }) - - // Holds how much bytes we read, also where the checksum (should) start - const inflatedLen = engine.bytesRead - // It appears that mc sends extra bytes past the checksum. I don't think this is a raknet - // issue (as we are able to decipher properly, zlib works and should also have a checksum) so - // there needs to be more investigation done. If you know what's wrong here, please make an issue :) - const extraneousLen = chunk.length - inflatedLen - 8 - if (extraneousLen > 0) { // Extra bytes - // Info for debugging, todo: use debug() - const extraneousBytes = chunk.slice(inflatedLen + 8) - console.debug('Extraneous bytes!', extraneousLen, extraneousBytes.toString('hex')) - } else if (extraneousLen < 0) { - // No checksum or decompression failed - console.warn('Failed to decrypt', chunk.toString('hex')) - throw new Error('Decrypted packet is missing checksum') - } - - const packet = chunk.slice(0, inflatedLen) - const checksum = chunk.slice(inflatedLen, inflatedLen + 8) + const packet = chunk.slice(0, chunk.length - 8) + const checksum = chunk.slice(chunk.length - 8, chunk.length) const computedCheckSum = computeCheckSum(packet, client.receiveCounter, client.secretKeyBytes) client.receiveCounter++ - if (checksum.toString('hex') === computedCheckSum.toString('hex')) { - client.onDecryptedPacket(buffer) - } else { - console.log('Inflated', inflatedLen, chunk.length, extraneousLen, chunk.toString('hex')) + if (Buffer.compare(checksum, computedCheckSum) !== 0) { + // console.log('Inflated', inflatedLen, chunk.length, extraneousLen, chunk.toString('hex')) throw Error(`Checksum mismatch ${checksum.toString('hex')} != ${computedCheckSum.toString('hex')}`) } + + Zlib.inflateRaw(chunk, { chunkSize: 1024 * 1024 * 2 }, (err, buffer) => { + if (err) throw err + client.onDecryptedPacket(buffer) + }) } client.decipher.on('data', verify) diff --git a/data/new/compile.js b/tools/compileProtocol.js similarity index 100% rename from data/new/compile.js rename to tools/compileProtocol.js