Login refactoring (#45)

* Refactor auth + encryption

Fix a bug with client account token caching

* move some files
This commit is contained in:
extremeheat 2021-03-13 13:50:16 -05:00 committed by GitHub
commit 58e011e06d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 218 additions and 333 deletions

View file

@ -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

View file

@ -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?

View file

@ -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')

View file

@ -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()

View file

@ -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) {

73
src/auth/login.js Normal file
View file

@ -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 } })
}
}

83
src/auth/loginVerify.js Normal file
View file

@ -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
}

View file

@ -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)

View file

@ -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))

View file

@ -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}`)

View file

@ -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

View file

@ -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)