Remove crypto deps

Use node crypto lib
This commit is contained in:
extremeheat 2021-05-11 21:09:35 -04:00
commit b8f6ab4ed3
4 changed files with 37 additions and 83 deletions

View file

@ -23,11 +23,9 @@
"dependencies": {
"@azure/msal-node": "^1.0.0-beta.6",
"@xboxreplay/xboxlive-auth": "^3.3.3",
"asn1": "^0.2.4",
"debug": "^4.3.1",
"ec-pem": "^0.18.0",
"jsonwebtoken": "^8.5.1",
"jsp-raknet": "^2.0.0",
"jsp-raknet": "^2.1.0",
"minecraft-folder-path": "^1.1.0",
"node-fetch": "^2.6.1",
"prismarine-nbt": "^1.5.0",

View file

@ -1,27 +1,26 @@
const { Ber } = require('asn1')
const { ClientStatus } = require('../connection')
const JWT = require('jsonwebtoken')
const crypto = require('crypto')
const ecPem = require('ec-pem')
const debug = require('debug')('minecraft-protocol')
const SALT = '🧂'
const curve = 'secp384r1'
const pem = { format: 'pem', type: 'sec1' }
const der = { format: 'der', type: 'spki' }
function Encrypt (client, server, options) {
client.ecdhKeyPair = crypto.createECDH(curve)
client.ecdhKeyPair.generateKeys()
client.clientX509 = writeX509PublicKey(client.ecdhKeyPair.getPublicKey())
client.ecdhKeyPair = crypto.generateKeyPairSync('ec', { namedCurve: curve })
client.publicKeyDER = client.ecdhKeyPair.publicKey.export(der)
client.privateKeyPEM = client.ecdhKeyPair.privateKey.export(pem)
console.log(client.publicKeyPEM)
client.clientX509 = client.publicKeyDER.toString('base64')
function startClientboundEncryption (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()
const pubKeyDer = crypto.createPublicKey({ key: Buffer.from(publicKey.key, 'base64'), ...der })
// Shared secret from the client's public key + our private key
client.sharedSecret = alice.computeSecret(pubKeyBuf)
client.sharedSecret = crypto.diffieHellman({ privateKey: client.ecdhKeyPair.privateKey, publicKey: pubKeyDer })
// Secret hash we use for packet encryption:
// From the public key of the remote and the private key
@ -36,15 +35,12 @@ function Encrypt (client, server, options) {
client.secretKeyBytes = secretHash.digest()
// console.log('[encrypt] Shared hash', client.secretKeyBytes)
const x509 = writeX509PublicKey(alice.getPublicKey())
const token = JWT.sign({
salt: toBase64(SALT),
signedToken: alice.getPublicKey('base64')
}, alicePEMPrivate, { algorithm: 'ES384', header: { x5u: x509 } })
signedToken: client.clientX509
}, client.ecdhKeyPair.privateKey, { algorithm: 'ES384', header: { x5u: client.clientX509 } })
client.write('server_to_client_handshake', {
token: token
})
client.write('server_to_client_handshake', { token })
// The encryption scheme is AES/CFB8/NoPadding with the
// secret key being the result of the sha256 above and
@ -57,28 +53,27 @@ function Encrypt (client, server, options) {
debug('[encrypt] Starting serverbound encryption', token)
const jwt = token?.token
if (!jwt) {
// TODO: allow connecting to servers without encryption
throw Error('Server did not return a valid JWT, cannot start encryption!')
}
// TODO: Should we do some JWT signature validation here? Seems pointless
const alice = client.ecdhKeyPair
const [header, payload] = jwt.split('.').map(k => Buffer.from(k, 'base64'))
const head = JSON.parse(String(header))
const body = JSON.parse(String(payload))
const serverPublicKey = readX509PublicKey(head.x5u)
client.sharedSecret = alice.computeSecret(serverPublicKey)
// console.log('[encrypt] Shared secret', client.sharedSecret)
const pubKeyDer = crypto.createPublicKey({ key: Buffer.from(head.x5u, 'base64'), ...der })
// Shared secret from the client's public key + our private key
client.sharedSecret = crypto.diffieHellman({ privateKey: client.ecdhKeyPair.privateKey, publicKey: pubKeyDer })
const salt = Buffer.from(body.salt, 'base64')
const secretHash = crypto.createHash('sha256')
secretHash.update(salt)
secretHash.update(client.sharedSecret)
client.secretKeyBytes = secretHash.digest()
// console.log('[encrypt] Shared hash', client.secretKeyBytes)
const initial = client.secretKeyBytes.slice(0, 16)
client.startEncryption(initial)
const iv = client.secretKeyBytes.slice(0, 16)
client.startEncryption(iv)
// It works! First encrypted packet :)
client.write('client_to_server_handshake', {})
@ -94,29 +89,4 @@ function toBase64 (string) {
return Buffer.from(string).toString('base64')
}
function readX509PublicKey (key) {
const reader = new Ber.Reader(Buffer.from(key, 'base64'))
reader.readSequence()
reader.readSequence()
reader.readOID() // Hey, I'm an elliptic curve
reader.readOID() // This contains the curve type, could be useful
return Buffer.from(reader.readString(Ber.BitString, true)).slice(1)
}
function writeX509PublicKey (key) {
const writer = new Ber.Writer()
writer.startSequence()
writer.startSequence()
writer.writeOID('1.2.840.10045.2.1')
writer.writeOID('1.3.132.0.34')
writer.endSequence()
writer.writeBuffer(Buffer.concat([Buffer.from([0x00]), key]), Ber.BitString)
writer.endSequence()
return writer.buffer.toString('base64')
}
module.exports = {
readX509PublicKey,
writeX509PublicKey,
Encrypt
}
module.exports = { Encrypt }

View file

@ -1,18 +1,15 @@
const fs = require('fs')
const JWT = require('jsonwebtoken')
const DataProvider = require('../../data/provider')
const ecPem = require('ec-pem')
const curve = 'secp384r1'
const { nextUUID } = require('../datatypes/util')
const { PUBLIC_KEY } = require('./constants')
const algorithm = 'ES384'
module.exports = (client, server, options) => {
const skinGeom = fs.readFileSync(DataProvider(options.protocolVersion).getPath('skin_geom.txt'), 'utf-8')
client.createClientChain = (mojangKey, offline) => {
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 privateKey = client.ecdhKeyPair.privateKey
let token
if (offline) {
@ -25,16 +22,16 @@ module.exports = (client, server, options) => {
certificateAuthority: true,
identityPublicKey: client.clientX509
}
token = JWT.sign(payload, alicePEMPrivate, { algorithm: 'ES384', notBefore: 0, issuer: 'self', expiresIn: 60 * 60, header: { x5u: client.clientX509 } })
token = JWT.sign(payload, privateKey, { algorithm, notBefore: 0, issuer: 'self', expiresIn: 60 * 60, header: { x5u: client.clientX509 } })
} else {
token = JWT.sign({
identityPublicKey: mojangKey,
identityPublicKey: mojangKey || PUBLIC_KEY,
certificateAuthority: true
}, alicePEMPrivate, { algorithm: 'ES384', header: { x5u: client.clientX509 } })
}, privateKey, { algorithm, header: { x5u: client.clientX509 } })
}
client.clientIdentityChain = token
client.createClientUserChain(alicePEMPrivate)
client.createClientUserChain(privateKey)
}
client.createClientUserChain = (privateKey) => {
@ -82,7 +79,6 @@ module.exports = (client, server, options) => {
const customPayload = options.skinData || {}
payload = { ...payload, ...customPayload }
client.clientUserChain = JWT.sign(payload, privateKey,
{ algorithm: 'ES384', header: { x5u: client.clientX509 } })
client.clientUserChain = JWT.sign(payload, privateKey, { algorithm, header: { x5u: client.clientX509 } })
}
}

View file

@ -1,11 +1,14 @@
const JWT = require('jsonwebtoken')
const constants = require('./constants')
const debug = require('debug')('minecraft-protocol')
const crypto = require('crypto')
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
const getDER = b64 => crypto.createPublicKey({ key: Buffer.from(b64, 'base64'), format: 'der', type: 'spki' })
function verifyAuth (chain) {
let data = {}
@ -16,9 +19,9 @@ module.exports = (client, server, options) => {
// 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 pubKey = getDER(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)
@ -30,7 +33,7 @@ module.exports = (client, server, options) => {
debug('Verified client with mojang key', x5u)
}
pubKey = decoded.identityPublicKey ? mcPubKeyToPem(decoded.identityPublicKey) : x5u
pubKey = decoded.identityPublicKey ? getDER(decoded.identityPublicKey) : x5u
finalKey = decoded.identityPublicKey || finalKey // non pem
data = { ...data, ...decoded }
}
@ -44,7 +47,7 @@ module.exports = (client, server, options) => {
}
function verifySkin (publicKey, token) {
const pubKey = mcPubKeyToPem(publicKey)
const pubKey = getDER(publicKey)
const decoded = JWT.verify(token, pubKey, { algorithms: ['ES384'] })
return decoded
}
@ -71,16 +74,3 @@ function getX5U (token) {
const hjson = JSON.parse(hdec)
return hjson.x5u
}
function mcPubKeyToPem (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
}