Skip to content

Commit 3cf0d98

Browse files
committed
Implement player certificate fetching for Mojang auth
Borrows the fetchCertificates implementation from prismarine-auth
1 parent bf89f7e commit 3cf0d98

2 files changed

Lines changed: 47 additions & 2 deletions

File tree

src/client/mojangAuth.js

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ const yggdrasil = require('yggdrasil')
33
const fs = require('fs').promises
44
const mcDefaultFolderPath = require('minecraft-folder-path')
55
const path = require('path')
6+
const crypto = require('crypto')
67

78
const launcherDataFile = 'launcher_accounts.json'
89

@@ -33,6 +34,32 @@ module.exports = async function (client, options) {
3334
}
3435
}
3536

37+
// Adapted from https://github.com/PrismarineJS/prismarine-auth/blob/1aef6e1387d94fca839f2811d17ac6659ae556b4/src/TokenManagers/MinecraftJavaTokenManager.js#L101
38+
const toDER = pem => pem.split('\n').slice(1, -1).reduce((acc, cur) => Buffer.concat([acc, Buffer.from(cur, 'base64')]), Buffer.alloc(0))
39+
async function fetchCertificates (accessToken) {
40+
const servicesServer = options.servicesServer ?? 'https://api.minecraftservices.com'
41+
const headers = {
42+
'Content-Type': 'application/json',
43+
Authorization: `Bearer ${accessToken}`
44+
}
45+
const res = await fetch(`${servicesServer}/player/certificates`, { headers, method: 'post' })
46+
if (!res.ok) throw Error(`Certificates request returned status ${res.status}`)
47+
const cert = await res.json()
48+
const profileKeys = {
49+
publicPEM: cert.keyPair.publicKey,
50+
privatePEM: cert.keyPair.privateKey,
51+
publicDER: toDER(cert.keyPair.publicKey),
52+
privateDER: toDER(cert.keyPair.privateKey),
53+
signature: Buffer.from(cert.publicKeySignature, 'base64'),
54+
signatureV2: Buffer.from(cert.publicKeySignatureV2, 'base64'),
55+
expiresOn: new Date(cert.expiresAt),
56+
refreshAfter: new Date(cert.refreshedAfter)
57+
}
58+
profileKeys.public = crypto.createPublicKey({ key: profileKeys.publicDER, format: 'der', type: 'spki' })
59+
profileKeys.private = crypto.createPrivateKey({ key: profileKeys.privateDER, format: 'der', type: 'pkcs8' })
60+
return { profileKeys }
61+
}
62+
3663
function getProfileId (auths) {
3764
try {
3865
const lowerUsername = options.username.toLowerCase()
@@ -105,8 +132,25 @@ module.exports = async function (client, options) {
105132
client.session = session
106133
client.username = session.selectedProfile.name
107134
options.accessToken = session.accessToken
108-
client.emit('session', session)
109-
options.connect(client)
135+
136+
const finishCb = function (certificates) {
137+
if (certificates !== null) {
138+
Object.assign(client, certificates)
139+
}
140+
client.emit('session', session)
141+
options.connect(client)
142+
}
143+
144+
if (options.disableChatSigning) {
145+
finishCb(null)
146+
} else {
147+
fetchCertificates(session.accessToken)
148+
.catch(e => {
149+
console.warn(`Failed to fetch player certificates: ${e}`)
150+
return null
151+
})
152+
.then(finishCb)
153+
}
110154
}
111155
}
112156

src/index.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ declare module 'minecraft-protocol' {
132132
accessToken?: string
133133
authServer?: string
134134
authTitle?: string
135+
servicesServer?: string
135136
sessionServer?: string
136137
keepAlive?: boolean
137138
closeTimeout?: number

0 commit comments

Comments
 (0)