Skip to content

Commit 3176d91

Browse files
committed
Implement player certificate fetching for Mojang auth
Borrows the fetchCertificates implementation from prismarine-auth
1 parent 6c2204a commit 3176d91

File tree

2 files changed

+37
-1
lines changed

2 files changed

+37
-1
lines changed

src/client/mojangAuth.js

Lines changed: 36 additions & 1 deletion
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()
@@ -47,7 +74,7 @@ module.exports = async function (client, options) {
4774

4875
if (options.haveCredentials) {
4976
// make a request to get the case-correct username before connecting.
50-
const cb = function (err, session) {
77+
const cb = async function (err, session) {
5178
if (options.profilesFolder) {
5279
getLauncherProfiles().then((auths) => {
5380
if (!auths.accounts) auths.accounts = []
@@ -104,6 +131,14 @@ module.exports = async function (client, options) {
104131
} else {
105132
client.session = session
106133
client.username = session.selectedProfile.name
134+
if (!options.disableChatSigning) {
135+
try {
136+
const certificates = await fetchCertificates(session.accessToken)
137+
Object.assign(client, certificates)
138+
} catch (e) {
139+
console.warn(`Failed to fetch player certificates: ${e}`)
140+
}
141+
}
107142
options.accessToken = session.accessToken
108143
client.emit('session', session)
109144
options.connect(client)

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)