Skip to content

Commit

Permalink
Merge pull request #10 from YuxuanZuo/develop
Browse files Browse the repository at this point in the history
Release v0.2.0
  • Loading branch information
YuxuanZuo authored Jan 23, 2023
2 parents 0275a7f + 3aabb88 commit 1a132f6
Show file tree
Hide file tree
Showing 17 changed files with 534 additions and 277 deletions.
3 changes: 0 additions & 3 deletions README.en.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,9 +103,6 @@ Configure Minecraft server with the following JVM parameter:
If this this feature is enabled, Minecraft will send a POST request to /minecraftservices/player/certificates to retrieve the key pair issued by the authentication server.
It's disabled by default if the authentication server does NOT send feature.enable_profile_key option.
If the profile signing key isn't present, the player will be unable to join servers that enable enforce-secure-profile=true option.
And other players' Minecraft client will log a warning message when receiving an unsigned chat message.
-Dauthlibinjector.usernameCheck={default|enabled|disabled}
Whether to enable username validation. If disabled, Minecraft, BungeeCord and Paper will NOT perform username validation.
It's disabled by default if the authentication server does NOT send feature.usernameCheck option.
Expand Down
3 changes: 0 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,9 +110,6 @@ gradle
启用此功能后, Minecraft 会向 /minecraftservices/player/certificates 发送 POST 请求, 以获取由验证服务器颁发的密钥对.
此功能需要验证服务器支持, 若验证服务器未设置 feature.enable_profile_key 选项, 则该功能默认禁用.
当缺少消息签名密钥时, 玩家将无法进入设置了 enforce-secure-profile=true 选项的服务器.
而当其他玩家的客户端在收到无有效签名的聊天消息时, 会在日志中记录警告.
-Dauthlibinjector.usernameCheck={default|enabled|disabled}
是否启用玩家用户名检查, 若禁用, 则 authlib-injector 将关闭 Minecraft、BungeeCord 和 Paper 的用户名检查功能.
若验证服务器未设置 feature.usernameCheck 选项, 则默认禁用.
Expand Down
4 changes: 4 additions & 0 deletions src/main/java/xyz/zuoyx/multiyggdrasil/MultiYggdrasil.java
Original file line number Diff line number Diff line change
Expand Up @@ -62,13 +62,15 @@
import xyz.zuoyx.multiyggdrasil.transform.support.AuthServerNameInjector;
import xyz.zuoyx.multiyggdrasil.transform.support.AuthlibLogInterceptor;
import xyz.zuoyx.multiyggdrasil.transform.support.BungeeCordAllowedCharactersTransformer;
import xyz.zuoyx.multiyggdrasil.transform.support.BungeeCordProfileKeyTransformUnit;
import xyz.zuoyx.multiyggdrasil.transform.support.CitizensTransformer;
import xyz.zuoyx.multiyggdrasil.transform.support.ConstantURLTransformUnit;
import xyz.zuoyx.multiyggdrasil.transform.support.MainArgumentsTransformer;
import xyz.zuoyx.multiyggdrasil.transform.support.PaperUsernameCheckTransformer;
import xyz.zuoyx.multiyggdrasil.transform.support.ProxyParameterWorkaround;
import xyz.zuoyx.multiyggdrasil.transform.support.SkinWhitelistTransformUnit;
import xyz.zuoyx.multiyggdrasil.transform.support.UsernameCharacterCheckTransformer;
import xyz.zuoyx.multiyggdrasil.transform.support.VelocityProfileKeyTransformUnit;
import xyz.zuoyx.multiyggdrasil.transform.support.YggdrasilKeyTransformUnit;
import xyz.zuoyx.multiyggdrasil.transform.support.HasJoinedServerTransformer;
import xyz.zuoyx.multiyggdrasil.transform.support.HasJoinedServerResponseTransformer;
Expand Down Expand Up @@ -318,6 +320,8 @@ private static ClassTransformer createTransformer(APIMetadata config) {

transformer.units.add(new YggdrasilKeyTransformUnit());
config.getDecodedPublickey().ifPresent(YggdrasilKeyTransformUnit.PUBLIC_KEYS::add);
transformer.units.add(new VelocityProfileKeyTransformUnit());
transformer.units.add(new BungeeCordProfileKeyTransformUnit());

return transformer;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,18 @@
*/
package xyz.zuoyx.multiyggdrasil.httpd;

import static java.nio.charset.StandardCharsets.UTF_8;
import static xyz.zuoyx.multiyggdrasil.util.IOUtils.CONTENT_TYPE_JSON;
import static xyz.zuoyx.multiyggdrasil.util.JsonUtils.toJsonString;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.time.Instant;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.Base64;
import java.util.Optional;
import com.google.gson.JsonObject;
import xyz.zuoyx.multiyggdrasil.internal.fi.iki.elonen.IHTTPSession;
import xyz.zuoyx.multiyggdrasil.internal.fi.iki.elonen.Response;
import xyz.zuoyx.multiyggdrasil.internal.fi.iki.elonen.Status;
Expand All @@ -35,9 +46,39 @@ public boolean canHandle(String domain) {
@Override
public Optional<Response> handle(String domain, String path, IHTTPSession session) {
if (domain.equals("api.minecraftservices.com") && path.equals("/player/certificates") && session.getMethod().equals("POST")) {
return Optional.of(Response.newFixedLength(Status.NO_CONTENT, null, null));
return Optional.of(Response.newFixedLength(Status.OK, CONTENT_TYPE_JSON, toJsonString(makeDummyResponse())));
}
return Optional.empty();
}

private JsonObject makeDummyResponse() {
KeyPairGenerator generator;
try {
generator = KeyPairGenerator.getInstance("RSA");
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
generator.initialize(2048);
KeyPair keyPair = generator.generateKeyPair();

Base64.Encoder base64 = Base64.getMimeEncoder(76, "\n".getBytes(UTF_8));
String publicKeyPEM = "-----BEGIN RSA PUBLIC KEY-----\n" + base64.encodeToString(keyPair.getPublic().getEncoded()) + "\n-----END RSA PUBLIC KEY-----\n";
String privateKeyPEM = "-----BEGIN RSA PRIVATE KEY-----\n" + base64.encodeToString(keyPair.getPrivate().getEncoded()) + "\n-----END RSA PRIVATE KEY-----\n";

Instant now = Instant.now();
Instant expiresAt = now.plus(48, ChronoUnit.HOURS);
Instant refreshedAfter = now.plus(36, ChronoUnit.HOURS);

JsonObject response = new JsonObject();
JsonObject keyPairObj = new JsonObject();
keyPairObj.addProperty("privateKey", privateKeyPEM);
keyPairObj.addProperty("publicKey", publicKeyPEM);
response.add("keyPair", keyPairObj);
response.addProperty("publicKeySignature", "AA==");
response.addProperty("publicKeySignatureV2", "AA==");
response.addProperty("expiresAt", DateTimeFormatter.ISO_INSTANT.format(expiresAt));
response.addProperty("refreshedAfter", DateTimeFormatter.ISO_INSTANT.format(refreshedAfter));
return response;
}

}

This file was deleted.

155 changes: 146 additions & 9 deletions src/main/java/xyz/zuoyx/multiyggdrasil/transform/CallbackSupport.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2021 Haowei Wen <yushijinhun@gmail.com> and contributors
* Copyright (C) 2022 Haowei Wen <yushijinhun@gmail.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
Expand All @@ -16,17 +16,42 @@
*/
package xyz.zuoyx.multiyggdrasil.transform;

import static org.objectweb.asm.Opcodes.AASTORE;
import static org.objectweb.asm.Opcodes.ACC_PRIVATE;
import static org.objectweb.asm.Opcodes.ACC_STATIC;
import static org.objectweb.asm.Opcodes.ACC_SYNTHETIC;
import static org.objectweb.asm.Opcodes.ALOAD;
import static org.objectweb.asm.Opcodes.ANEWARRAY;
import static org.objectweb.asm.Opcodes.ARETURN;
import static org.objectweb.asm.Opcodes.DLOAD;
import static org.objectweb.asm.Opcodes.DRETURN;
import static org.objectweb.asm.Opcodes.DUP;
import static org.objectweb.asm.Opcodes.FLOAD;
import static org.objectweb.asm.Opcodes.FRETURN;
import static org.objectweb.asm.Opcodes.GETSTATIC;
import static org.objectweb.asm.Opcodes.H_INVOKESTATIC;
import static org.objectweb.asm.Opcodes.ILOAD;
import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
import static org.objectweb.asm.Opcodes.INVOKESTATIC;
import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL;
import static org.objectweb.asm.Opcodes.IRETURN;
import static org.objectweb.asm.Opcodes.LLOAD;
import static org.objectweb.asm.Opcodes.LRETURN;
import static org.objectweb.asm.Opcodes.NEW;
import static org.objectweb.asm.Opcodes.RETURN;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.Handle;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Type;

public final class CallbackSupport {
final class CallbackSupport {
private CallbackSupport() {
}

static final String METAFACTORY_NAME = "__multiyggdrasil_metafactory";
static final String METAFACTORY_SIGNATURE = "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;)Ljava/lang/invoke/CallSite;";
private static final String METAFACTORY_NAME = "__multiyggdrasil_metafactory";
private static final String METAFACTORY_SIGNATURE = "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;)Ljava/lang/invoke/CallSite;";

private static Method findCallbackMethod(Class<?> owner, String methodName) {
for (Method method : owner.getDeclaredMethods()) {
Expand All @@ -40,11 +65,123 @@ private static Method findCallbackMethod(Class<?> owner, String methodName) {
throw new IllegalArgumentException("No such method: " + methodName);
}

public static void invoke(TransformContext ctx, MethodVisitor mv, Class<?> owner, String methodName) {
ctx.requireMinimumClassVersion(50);
ctx.upgradeClassVersion(51);

static void callWithInvokeDynamic(MethodVisitor mv, Class<?> owner, String methodName, TransformContext ctx) {
String descriptor = Type.getMethodDescriptor(findCallbackMethod(owner, methodName));
mv.visitInvokeDynamicInsn(methodName, descriptor, ctx.acquireCallbackMetafactory(), owner.getName());
Handle callbackMetafactory = new Handle(
H_INVOKESTATIC,
ctx.getClassName().replace('.', '/'),
CallbackSupport.METAFACTORY_NAME,
CallbackSupport.METAFACTORY_SIGNATURE,
ctx.isInterface());
mv.visitInvokeDynamicInsn(methodName, descriptor, callbackMetafactory, owner.getName());
}

static void callWithIntermediateMethod(MethodVisitor mv0, Class<?> owner, String methodName, TransformContext ctx) {
Method callbackMethod = findCallbackMethod(owner, methodName);
String descriptor = Type.getMethodDescriptor(callbackMethod);
String intermediateMethod = "__authlibinjector_intermediate__" + owner.getName().replace('.', '_') + "__" + methodName;
mv0.visitMethodInsn(INVOKESTATIC, ctx.getClassName().replace('.', '/'), intermediateMethod, descriptor, ctx.isInterface());

ctx.addGeneratedMethod(intermediateMethod, cv -> {
int paramNum = callbackMethod.getParameterCount();
Class<?>[] paramTypes = callbackMethod.getParameterTypes();
Class<?> returnType = callbackMethod.getReturnType();

MethodVisitor mv = cv.visitMethod(ACC_PRIVATE | ACC_STATIC | ACC_SYNTHETIC, intermediateMethod, descriptor, null, null);
mv.visitCode();
mv.visitMethodInsn(INVOKESTATIC, "java/lang/invoke/MethodHandles", "publicLookup", "()Ljava/lang/invoke/MethodHandles$Lookup;", false);
mv.visitMethodInsn(INVOKESTATIC, "java/lang/ClassLoader", "getSystemClassLoader", "()Ljava/lang/ClassLoader;", false);
mv.visitLdcInsn(owner.getName());
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/ClassLoader", "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;", false);
mv.visitLdcInsn(methodName);
pushType(mv, returnType);
mv.visitLdcInsn(paramNum);
mv.visitTypeInsn(ANEWARRAY, "java/lang/Class");
for (int i = 0; i < paramNum; i++) {
mv.visitInsn(DUP);
mv.visitLdcInsn(i);
pushType(mv, paramTypes[i]);
mv.visitInsn(AASTORE);
}
mv.visitMethodInsn(INVOKESTATIC, "java/lang/invoke/MethodType", "methodType", "(Ljava/lang/Class;[Ljava/lang/Class;)Ljava/lang/invoke/MethodType;", false);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/invoke/MethodHandles$Lookup", "findStatic", "(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/MethodHandle;", false);
for (int i = 0; i < paramNum; i++) {
Class<?> type = paramTypes[i];
if (type == boolean.class || type == byte.class || type == char.class || type == short.class || type == int.class) {
mv.visitVarInsn(ILOAD, i);
} else if (type == long.class) {
mv.visitVarInsn(LLOAD, i);
} else if (type == float.class) {
mv.visitVarInsn(FLOAD, i);
} else if (type == double.class) {
mv.visitVarInsn(DLOAD, i);
} else {
mv.visitVarInsn(ALOAD, i);
}
}
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/invoke/MethodHandle", "invokeExact", descriptor, false);
if (returnType == void.class) {
mv.visitInsn(RETURN);
} else if (returnType == boolean.class || returnType == byte.class || returnType == char.class || returnType == short.class || returnType == int.class) {
mv.visitInsn(IRETURN);
} else if (returnType == long.class) {
mv.visitInsn(LRETURN);
} else if (returnType == float.class) {
mv.visitInsn(FRETURN);
} else if (returnType == double.class) {
mv.visitInsn(DRETURN);
} else {
mv.visitInsn(ARETURN);
}
mv.visitMaxs(-1, -1);
mv.visitEnd();
});
}

private static void pushType(MethodVisitor mv, Class<?> type) {
if (type.isPrimitive()) {
if (type == boolean.class) {
mv.visitFieldInsn(GETSTATIC, "java/lang/Boolean", "TYPE", "Ljava/lang/Class;");
} else if (type == byte.class) {
mv.visitFieldInsn(GETSTATIC, "java/lang/Byte", "TYPE", "Ljava/lang/Class;");
} else if (type == char.class) {
mv.visitFieldInsn(GETSTATIC, "java/lang/Character", "TYPE", "Ljava/lang/Class;");
} else if (type == short.class) {
mv.visitFieldInsn(GETSTATIC, "java/lang/Short", "TYPE", "Ljava/lang/Class;");
} else if (type == int.class) {
mv.visitFieldInsn(GETSTATIC, "java/lang/Integer", "TYPE", "Ljava/lang/Class;");
} else if (type == float.class) {
mv.visitFieldInsn(GETSTATIC, "java/lang/Float", "TYPE", "Ljava/lang/Class;");
} else if (type == long.class) {
mv.visitFieldInsn(GETSTATIC, "java/lang/Long", "TYPE", "Ljava/lang/Class;");
} else if (type == double.class) {
mv.visitFieldInsn(GETSTATIC, "java/lang/Double", "TYPE", "Ljava/lang/Class;");
} else if (type == void.class) {
mv.visitFieldInsn(GETSTATIC, "java/lang/Void", "TYPE", "Ljava/lang/Class;");
}
} else {
mv.visitLdcInsn(Type.getType(type));
}
}

static void insertMetafactory(ClassVisitor visitor) {
MethodVisitor mv = visitor.visitMethod(ACC_PRIVATE | ACC_STATIC | ACC_SYNTHETIC,
CallbackSupport.METAFACTORY_NAME,
CallbackSupport.METAFACTORY_SIGNATURE,
null, null);
mv.visitCode();
mv.visitTypeInsn(NEW, "java/lang/invoke/ConstantCallSite");
mv.visitInsn(DUP);
mv.visitVarInsn(ALOAD, 0);
mv.visitMethodInsn(INVOKESTATIC, "java/lang/ClassLoader", "getSystemClassLoader", "()Ljava/lang/ClassLoader;", false);
mv.visitVarInsn(ALOAD, 3);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/ClassLoader", "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;", false);
mv.visitVarInsn(ALOAD, 1);
mv.visitVarInsn(ALOAD, 2);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/invoke/MethodHandles$Lookup", "findStatic", "(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/MethodHandle;", false);
mv.visitMethodInsn(INVOKESPECIAL, "java/lang/invoke/ConstantCallSite", "<init>", "(Ljava/lang/invoke/MethodHandle;)V", false);
mv.visitInsn(ARETURN);
mv.visitMaxs(-1, -1);
mv.visitEnd();
}
}
Loading

0 comments on commit 1a132f6

Please sign in to comment.