Remove crypto deps
Use node crypto lib
This commit is contained in:
parent
3a4335a2ae
commit
b8f6ab4ed3
4 changed files with 37 additions and 83 deletions
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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 } })
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue