@@ -3,6 +3,7 @@ const yggdrasil = require('yggdrasil')
3
3
const fs = require ( 'fs' ) . promises
4
4
const mcDefaultFolderPath = require ( 'minecraft-folder-path' )
5
5
const path = require ( 'path' )
6
+ const crypto = require ( 'crypto' )
6
7
7
8
const launcherDataFile = 'launcher_accounts.json'
8
9
@@ -33,6 +34,32 @@ module.exports = async function (client, options) {
33
34
}
34
35
}
35
36
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
+
36
63
function getProfileId ( auths ) {
37
64
try {
38
65
const lowerUsername = options . username . toLowerCase ( )
@@ -47,7 +74,7 @@ module.exports = async function (client, options) {
47
74
48
75
if ( options . haveCredentials ) {
49
76
// make a request to get the case-correct username before connecting.
50
- const cb = function ( err , session ) {
77
+ const cb = async function ( err , session ) {
51
78
if ( options . profilesFolder ) {
52
79
getLauncherProfiles ( ) . then ( ( auths ) => {
53
80
if ( ! auths . accounts ) auths . accounts = [ ]
@@ -104,6 +131,14 @@ module.exports = async function (client, options) {
104
131
} else {
105
132
client . session = session
106
133
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
+ }
107
142
options . accessToken = session . accessToken
108
143
client . emit ( 'session' , session )
109
144
options . connect ( client )
0 commit comments