2022-08-15 23:55:44 +08:00
using System ;
using System.Collections.Generic ;
2022-08-27 02:10:44 +08:00
using System.Security.Cryptography ;
2022-08-15 23:55:44 +08:00
using System.Text ;
2023-01-13 16:12:10 +08:00
using MinecraftClient.Protocol.Handlers ;
2022-08-27 02:10:44 +08:00
using MinecraftClient.Protocol.Message ;
2023-01-13 16:12:10 +08:00
using static MinecraftClient . Protocol . Message . LastSeenMessageList ;
2024-01-13 03:00:02 +08:00
using Newtonsoft.Json.Linq ;
2022-08-15 23:55:44 +08:00
2022-12-06 15:50:17 +08:00
namespace MinecraftClient.Protocol.ProfileKey
2022-08-15 23:55:44 +08:00
{
static class KeyUtils
{
2022-08-27 02:10:44 +08:00
private static readonly SHA256 sha256Hash = SHA256 . Create ( ) ;
private static readonly string certificates = "https://api.minecraftservices.com/player/certificates" ;
2022-08-15 23:55:44 +08:00
2024-01-13 03:00:02 +08:00
public static PlayerKeyPair ? GetNewProfileKeys ( string accessToken , bool isYggdrasil )
2022-08-15 23:55:44 +08:00
{
ProxiedWebRequest . Response ? response = null ;
try
{
2024-01-13 03:00:02 +08:00
if ( ! isYggdrasil ) {
var request = new ProxiedWebRequest ( certificates )
{
Accept = "application/json"
} ;
request . Headers . Add ( "Authorization" , string . Format ( "Bearer {0}" , accessToken ) ) ;
2022-08-15 23:55:44 +08:00
2024-01-13 03:00:02 +08:00
response = request . Post ( "application/json" , "" ) ;
2022-08-15 23:55:44 +08:00
2024-01-13 03:00:02 +08:00
if ( Settings . Config . Logging . DebugMessages )
{
ConsoleIO . WriteLine ( response . Body . ToString ( ) ) ;
}
2022-08-15 23:55:44 +08:00
}
2024-01-13 03:00:02 +08:00
// see https://github.com/yushijinhun/authlib-injector/blob/da910956eaa30d2f6c2c457222d188aeb53b0d1f/src/main/java/moe/yushi/authlibinjector/httpd/ProfileKeyFilter.java#L49
// POST to "https://api.minecraftservices.com/player/certificates" with authlib-injector will get a dummy response
string jsonString = isYggdrasil ? MakeDummyResponse ( ) : response ! . Body ;
2022-08-15 23:55:44 +08:00
Json . JSONData json = Json . ParseJson ( jsonString ) ;
2024-01-13 03:00:02 +08:00
// Error here
2022-08-15 23:55:44 +08:00
PublicKey publicKey = new ( pemKey : json . Properties [ "keyPair" ] . Properties [ "publicKey" ] . StringValue ,
sig : json . Properties [ "publicKeySignature" ] . StringValue ,
sigV2 : json . Properties [ "publicKeySignatureV2" ] . StringValue ) ;
PrivateKey privateKey = new ( pemKey : json . Properties [ "keyPair" ] . Properties [ "privateKey" ] . StringValue ) ;
return new PlayerKeyPair ( publicKey , privateKey ,
expiresAt : json . Properties [ "expiresAt" ] . StringValue ,
refreshedAfter : json . Properties [ "refreshedAfter" ] . StringValue ) ;
}
catch ( Exception e )
{
2022-12-06 15:50:17 +08:00
int code = response = = null ? 0 : response . StatusCode ;
2022-08-15 23:55:44 +08:00
ConsoleIO . WriteLineFormatted ( "§cFetch profile key failed: HttpCode = " + code + ", Error = " + e . Message ) ;
2022-10-05 15:02:30 +08:00
if ( Settings . Config . Logging . DebugMessages )
2022-08-15 23:55:44 +08:00
{
ConsoleIO . WriteLineFormatted ( "§c" + e . StackTrace ) ;
}
return null ;
}
}
2022-12-06 15:50:17 +08:00
public static byte [ ] DecodePemKey ( string key , string prefix , string suffix )
2022-08-15 23:55:44 +08:00
{
int i = key . IndexOf ( prefix ) ;
if ( i ! = - 1 )
{
i + = prefix . Length ;
int j = key . IndexOf ( suffix , i ) ;
key = key [ i . . j ] ;
}
2022-12-06 15:50:17 +08:00
key = key . Replace ( "\r" , string . Empty ) ;
key = key . Replace ( "\n" , string . Empty ) ;
2022-08-15 23:55:44 +08:00
return Convert . FromBase64String ( key ) ;
}
2022-08-27 02:10:44 +08:00
public static byte [ ] ComputeHash ( byte [ ] data )
{
return sha256Hash . ComputeHash ( data ) ;
}
public static byte [ ] GetSignatureData ( string message , Guid uuid , DateTimeOffset timestamp , ref byte [ ] salt )
2022-08-15 23:55:44 +08:00
{
List < byte > data = new ( ) ;
data . AddRange ( salt ) ;
2022-08-27 02:10:44 +08:00
data . AddRange ( uuid . ToBigEndianBytes ( ) ) ;
byte [ ] timestampByte = BitConverter . GetBytes ( timestamp . ToUnixTimeSeconds ( ) ) ;
Array . Reverse ( timestampByte ) ;
data . AddRange ( timestampByte ) ;
data . AddRange ( Encoding . UTF8 . GetBytes ( message ) ) ;
return data . ToArray ( ) ;
}
public static byte [ ] GetSignatureData ( string message , DateTimeOffset timestamp , ref byte [ ] salt , LastSeenMessageList lastSeenMessages )
{
List < byte > data = new ( ) ;
2022-08-15 23:55:44 +08:00
2022-08-27 02:10:44 +08:00
data . AddRange ( salt ) ;
2022-08-15 23:55:44 +08:00
byte [ ] timestampByte = BitConverter . GetBytes ( timestamp . ToUnixTimeSeconds ( ) ) ;
Array . Reverse ( timestampByte ) ;
data . AddRange ( timestampByte ) ;
data . AddRange ( Encoding . UTF8 . GetBytes ( message ) ) ;
2022-08-27 02:10:44 +08:00
data . Add ( 70 ) ;
lastSeenMessages . WriteForSign ( data ) ;
return data . ToArray ( ) ;
}
2023-01-13 16:12:10 +08:00
public static byte [ ] GetSignatureData_1_19_3 ( string message , Guid playerUuid , Guid chatUuid , int messageIndex , DateTimeOffset timestamp , ref byte [ ] salt , AcknowledgedMessage [ ] lastSeenMessages )
{
List < byte > data = new ( ) ;
// net.minecraft.network.message.SignedMessage#update
data . AddRange ( DataTypes . GetInt ( 1 ) ) ;
// message link
// net.minecraft.network.message.MessageLink#update
data . AddRange ( DataTypes . GetUUID ( playerUuid ) ) ;
data . AddRange ( DataTypes . GetUUID ( chatUuid ) ) ;
data . AddRange ( DataTypes . GetInt ( messageIndex ) ) ;
// message body
// net.minecraft.network.message.MessageBody#update
data . AddRange ( salt ) ;
data . AddRange ( DataTypes . GetLong ( timestamp . ToUnixTimeSeconds ( ) ) ) ;
byte [ ] messageBytes = Encoding . UTF8 . GetBytes ( message ) ;
data . AddRange ( DataTypes . GetInt ( messageBytes . Length ) ) ;
data . AddRange ( messageBytes ) ;
data . AddRange ( DataTypes . GetInt ( lastSeenMessages . Length ) ) ;
foreach ( AcknowledgedMessage ack in lastSeenMessages )
data . AddRange ( ack . signature ) ;
return data . ToArray ( ) ;
}
2022-08-27 02:10:44 +08:00
public static byte [ ] GetSignatureData ( byte [ ] ? precedingSignature , Guid sender , byte [ ] bodySign )
{
List < byte > data = new ( ) ;
if ( precedingSignature ! = null )
data . AddRange ( precedingSignature ) ;
data . AddRange ( sender . ToBigEndianBytes ( ) ) ;
data . AddRange ( bodySign ) ;
2022-08-15 23:55:44 +08:00
return data . ToArray ( ) ;
}
2023-01-13 15:53:33 +08:00
public static byte [ ] GetSignatureData ( string message , DateTimeOffset timestamp , ref byte [ ] salt , int messageCount , Guid sender , Guid sessionUuid )
2023-01-11 17:25:25 +08:00
{
List < byte > data = new ( ) ;
// TODO!
2023-01-13 15:53:33 +08:00
byte [ ] unknownInt1 = BitConverter . GetBytes ( 1 ) ;
Array . Reverse ( unknownInt1 ) ;
data . AddRange ( unknownInt1 ) ;
data . AddRange ( sender . ToBigEndianBytes ( ) ) ;
data . AddRange ( sessionUuid . ToBigEndianBytes ( ) ) ;
byte [ ] msgCountByte = BitConverter . GetBytes ( messageCount ) ;
Array . Reverse ( msgCountByte ) ;
data . AddRange ( msgCountByte ) ;
data . AddRange ( salt ) ;
byte [ ] timestampByte = BitConverter . GetBytes ( timestamp . ToUnixTimeSeconds ( ) ) ;
Array . Reverse ( timestampByte ) ;
data . AddRange ( timestampByte ) ;
byte [ ] msgByte = Encoding . UTF8 . GetBytes ( message ) ;
byte [ ] msgLengthByte = BitConverter . GetBytes ( msgByte . Length ) ;
Array . Reverse ( msgLengthByte ) ;
data . AddRange ( msgLengthByte ) ;
data . AddRange ( msgByte ) ;
byte [ ] unknownInt2 = BitConverter . GetBytes ( 0 ) ;
Array . Reverse ( unknownInt2 ) ;
data . AddRange ( unknownInt2 ) ;
2023-01-11 17:25:25 +08:00
return data . ToArray ( ) ;
}
2022-08-15 23:55:44 +08:00
// https://github.com/mono/mono/blob/master/mcs/class/System.Json/System.Json/JsonValue.cs
public static string EscapeString ( string src )
{
StringBuilder sb = new ( ) ;
int start = 0 ;
for ( int i = 0 ; i < src . Length ; i + + )
{
char c = src [ i ] ;
bool needEscape = c < 32 | | c = = '"' | | c = = '\\' ;
// Broken lead surrogate
2022-12-06 15:50:17 +08:00
needEscape = needEscape | | c > = ' \ uD800 ' & & c < = ' \ uDBFF ' & &
( i = = src . Length - 1 | | src [ i + 1 ] < ' \ uDC00 ' | | src [ i + 1 ] > ' \ uDFFF ' ) ;
2022-08-15 23:55:44 +08:00
// Broken tail surrogate
2022-12-06 15:50:17 +08:00
needEscape = needEscape | | c > = ' \ uDC00 ' & & c < = ' \ uDFFF ' & &
( i = = 0 | | src [ i - 1 ] < ' \ uD800 ' | | src [ i - 1 ] > ' \ uDBFF ' ) ;
2022-08-15 23:55:44 +08:00
// To produce valid JavaScript
needEscape = needEscape | | c = = ' \ u2028 ' | | c = = ' \ u2029 ' ;
if ( needEscape )
{
sb . Append ( src , start , i - start ) ;
switch ( src [ i ] )
{
case '\b' : sb . Append ( "\\b" ) ; break ;
case '\f' : sb . Append ( "\\f" ) ; break ;
case '\n' : sb . Append ( "\\n" ) ; break ;
case '\r' : sb . Append ( "\\r" ) ; break ;
case '\t' : sb . Append ( "\\t" ) ; break ;
case '\"' : sb . Append ( "\\\"" ) ; break ;
case '\\' : sb . Append ( "\\\\" ) ; break ;
default :
sb . Append ( "\\u" ) ;
sb . Append ( ( ( int ) src [ i ] ) . ToString ( "x04" ) ) ;
break ;
}
start = i + 1 ;
}
}
sb . Append ( src , start , src . Length - start ) ;
return sb . ToString ( ) ;
}
2024-01-13 03:00:02 +08:00
public static string MakeDummyResponse ( )
{
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider ( 2048 ) ;
var mimePublicKey = Convert . ToBase64String ( rsa . ExportSubjectPublicKeyInfo ( ) ) ;
var mimePrivateKey = Convert . ToBase64String ( rsa . ExportPkcs8PrivateKey ( ) ) ;
string publicKeyPEM = $"-----BEGIN RSA PUBLIC KEY-----\n{mimePublicKey}\n-----END RSA PUBLIC KEY-----\n" ;
string privateKeyPEM = $"-----BEGIN RSA PRIVATE KEY-----\n{mimePrivateKey}\n-----END RSA PRIVATE KEY-----\n" ;
DateTime now = DateTime . UtcNow ;
DateTime expiresAt = now . AddHours ( 48 ) ;
DateTime refreshedAfter = now . AddHours ( 36 ) ;
JObject response = new JObject ( ) ;
JObject keyPairObj = new JObject
{
{ "privateKey" , privateKeyPEM } ,
{ "publicKey" , publicKeyPEM }
} ;
response . Add ( "keyPair" , keyPairObj ) ;
response . Add ( "publicKeySignature" , "AA==" ) ;
response . Add ( "publicKeySignatureV2" , "AA==" ) ;
string format = "yyyy-MM-ddTHH:mm:ss.ffffffZ" ;
response . Add ( "expiresAt" , expiresAt . ToString ( format ) ) ;
response . Add ( "refreshedAfter" , refreshedAfter . ToString ( format ) ) ;
return response . ToString ( ) ;
}
2022-08-15 23:55:44 +08:00
}
}