YggdrasilConfiguration -> APIMetadata
InjectorInitializationException -> InitializationException
javaagent/AuthlibInjectorPremain -> Premain
This commit is contained in:
yushijinhun 2020-08-22 16:26:22 +08:00
parent 496baee488
commit 1ba5bbb678
No known key found for this signature in database
GPG key ID: 5BC167F73EA558E4
9 changed files with 76 additions and 104 deletions

View file

@ -28,8 +28,8 @@ jar {
'Implementation-Vendor': 'yushijinhun', 'Implementation-Vendor': 'yushijinhun',
'Implementation-Timestamp': new Date().format("yyyy-MM-dd'T'HH:mm:ssZ"), 'Implementation-Timestamp': new Date().format("yyyy-MM-dd'T'HH:mm:ssZ"),
'Automatic-Module-Name': 'moe.yushi.authlibinjector', 'Automatic-Module-Name': 'moe.yushi.authlibinjector',
'Premain-Class': 'moe.yushi.authlibinjector.javaagent.AuthlibInjectorPremain', 'Premain-Class': 'moe.yushi.authlibinjector.Premain',
'Agent-Class': 'moe.yushi.authlibinjector.javaagent.AuthlibInjectorPremain', 'Agent-Class': 'moe.yushi.authlibinjector.Premain',
'Can-Retransform-Classes': true, 'Can-Retransform-Classes': true,
'Can-Redefine-Classes': true, 'Can-Redefine-Classes': true,
'Git-Commit': gitInfo.gitHashFull, 'Git-Commit': gitInfo.gitHashFull,

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2019 Haowei Wen <yushijinhun@gmail.com> and contributors * Copyright (C) 2020 Haowei Wen <yushijinhun@gmail.com> and contributors
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU Affero General Public License as published by
@ -37,9 +37,9 @@ import moe.yushi.authlibinjector.internal.org.json.simple.JSONObject;
import moe.yushi.authlibinjector.util.JsonUtils; import moe.yushi.authlibinjector.util.JsonUtils;
import moe.yushi.authlibinjector.util.KeyUtils; import moe.yushi.authlibinjector.util.KeyUtils;
public class YggdrasilConfiguration { public class APIMetadata {
public static YggdrasilConfiguration parse(String apiRoot, String metadataResponse) throws UncheckedIOException { public static APIMetadata parse(String apiRoot, String metadataResponse) throws UncheckedIOException {
JSONObject response = asJsonObject(parseJson(metadataResponse)); JSONObject response = asJsonObject(parseJson(metadataResponse));
List<String> skinDomains = List<String> skinDomains =
@ -59,7 +59,7 @@ public class YggdrasilConfiguration {
.map(it -> (Map<String, Object>) new TreeMap<>(asJsonObject(it))) .map(it -> (Map<String, Object>) new TreeMap<>(asJsonObject(it)))
.orElse(emptyMap()); .orElse(emptyMap());
return new YggdrasilConfiguration(apiRoot, unmodifiableList(skinDomains), unmodifiableMap(meta), decodedPublickey); return new APIMetadata(apiRoot, unmodifiableList(skinDomains), unmodifiableMap(meta), decodedPublickey);
} }
private String apiRoot; private String apiRoot;
@ -67,7 +67,7 @@ public class YggdrasilConfiguration {
private Optional<PublicKey> decodedPublickey; private Optional<PublicKey> decodedPublickey;
private Map<String, Object> meta; private Map<String, Object> meta;
public YggdrasilConfiguration(String apiRoot, List<String> skinDomains, Map<String, Object> meta, Optional<PublicKey> decodedPublickey) { public APIMetadata(String apiRoot, List<String> skinDomains, Map<String, Object> meta, Optional<PublicKey> decodedPublickey) {
this.apiRoot = requireNonNull(apiRoot); this.apiRoot = requireNonNull(apiRoot);
this.skinDomains = requireNonNull(skinDomains); this.skinDomains = requireNonNull(skinDomains);
this.meta = requireNonNull(meta); this.meta = requireNonNull(meta);
@ -92,7 +92,6 @@ public class YggdrasilConfiguration {
@Override @Override
public String toString() { public String toString() {
return format("YggdrasilConfiguration [apiRoot={0}, skinDomains={1}, decodedPublickey={2}, meta={3}]", apiRoot, skinDomains, decodedPublickey, meta); return format("APIMetadata [apiRoot={0}, skinDomains={1}, decodedPublickey={2}, meta={3}]", apiRoot, skinDomains, decodedPublickey, meta);
} }
} }

View file

@ -18,8 +18,7 @@ package moe.yushi.authlibinjector;
import static java.nio.charset.StandardCharsets.UTF_8; import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.Collections.emptyList; import static java.util.Collections.emptyList;
import static java.util.Optional.empty; import static java.util.Objects.requireNonNull;
import static java.util.Optional.of;
import static moe.yushi.authlibinjector.util.IOUtils.asBytes; import static moe.yushi.authlibinjector.util.IOUtils.asBytes;
import static moe.yushi.authlibinjector.util.IOUtils.asString; import static moe.yushi.authlibinjector.util.IOUtils.asString;
import static moe.yushi.authlibinjector.util.IOUtils.removeNewLines; import static moe.yushi.authlibinjector.util.IOUtils.removeNewLines;
@ -42,7 +41,6 @@ import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.function.Consumer;
import java.util.stream.Stream; import java.util.stream.Stream;
import moe.yushi.authlibinjector.httpd.DefaultURLRedirector; import moe.yushi.authlibinjector.httpd.DefaultURLRedirector;
import moe.yushi.authlibinjector.httpd.LegacySkinAPIFilter; import moe.yushi.authlibinjector.httpd.LegacySkinAPIFilter;
@ -65,12 +63,6 @@ import moe.yushi.authlibinjector.yggdrasil.MojangYggdrasilAPIProvider;
import moe.yushi.authlibinjector.yggdrasil.YggdrasilClient; import moe.yushi.authlibinjector.yggdrasil.YggdrasilClient;
public final class AuthlibInjector { public final class AuthlibInjector {
/**
* Stores the API URL, should be set before {@link #bootstrap(Consumer)} is invoked.
*/
public static final String PROP_API_ROOT = "authlibinjector.api";
private AuthlibInjector() {} private AuthlibInjector() {}
private static boolean booted = false; private static boolean booted = false;
@ -78,13 +70,13 @@ public final class AuthlibInjector {
private static boolean retransformSupported; private static boolean retransformSupported;
private static ClassTransformer classTransformer; private static ClassTransformer classTransformer;
public static synchronized void bootstrap(Instrumentation instrumentation) throws InjectorInitializationException { public static synchronized void bootstrap(Instrumentation instrumentation, String apiUrl) throws InitializationException {
if (booted) { if (booted) {
log(INFO, "Already started, skipping"); log(INFO, "Already started, skipping");
return; return;
} }
booted = true; booted = true;
AuthlibInjector.instrumentation = instrumentation; AuthlibInjector.instrumentation = requireNonNull(instrumentation);
Config.init(); Config.init();
retransformSupported = instrumentation.isRetransformClassesSupported(); retransformSupported = instrumentation.isRetransformClassesSupported();
@ -92,19 +84,14 @@ public final class AuthlibInjector {
log(WARNING, "Retransform is not supported"); log(WARNING, "Retransform is not supported");
} }
log(INFO, "Version: " + getVersion()); log(INFO, "Version: " + AuthlibInjector.class.getPackage().getImplementationVersion());
Optional<YggdrasilConfiguration> optionalConfig = configure(); APIMetadata apiMetadata = fetchAPIMetadata(apiUrl);
if (optionalConfig.isPresent()) { classTransformer = createTransformer(apiMetadata);
classTransformer = createTransformer(optionalConfig.get()); instrumentation.addTransformer(classTransformer, retransformSupported);
instrumentation.addTransformer(classTransformer, retransformSupported);
MC52974Workaround.init(); MC52974Workaround.init();
MC52974_1710Workaround.init(); MC52974_1710Workaround.init();
} else {
log(ERROR, "No authentication server specified");
throw new InjectorInitializationException();
}
} }
private static Optional<String> getPrefetchedResponse() { private static Optional<String> getPrefetchedResponse() {
@ -118,27 +105,40 @@ public final class AuthlibInjector {
return Optional.ofNullable(prefetched); return Optional.ofNullable(prefetched);
} }
private static Optional<YggdrasilConfiguration> configure() { private static APIMetadata fetchAPIMetadata(String apiUrl) {
String apiRoot = System.getProperty(PROP_API_ROOT); if (apiUrl == null || apiUrl.isEmpty()) {
if (apiRoot == null) log(ERROR, "No authentication server specified");
return empty(); throw new InitializationException();
}
apiRoot = addHttpsIfMissing(apiRoot); apiUrl = addHttpsIfMissing(apiUrl);
log(INFO, "Authentication server: " + apiRoot); log(INFO, "Authentication server: " + apiUrl);
warnIfHttp(apiRoot); warnIfHttp(apiUrl);
String metadataResponse; String metadataResponse;
Optional<String> prefetched = getPrefetchedResponse(); Optional<String> prefetched = getPrefetchedResponse();
if (!prefetched.isPresent()) { if (prefetched.isPresent()) {
log(DEBUG, "Prefetched metadata detected");
try {
metadataResponse = new String(Base64.getDecoder().decode(removeNewLines(prefetched.get())), UTF_8);
} catch (IllegalArgumentException e) {
log(ERROR, "Unable to decode metadata: " + e + "\n"
+ "Encoded metadata:\n"
+ prefetched.get());
throw new InitializationException(e);
}
} else {
try { try {
HttpURLConnection connection = (HttpURLConnection) new URL(apiRoot).openConnection(); HttpURLConnection connection = (HttpURLConnection) new URL(apiUrl).openConnection();
String ali = connection.getHeaderField("x-authlib-injector-api-location"); String ali = connection.getHeaderField("x-authlib-injector-api-location");
if (ali != null) { if (ali != null) {
URL absoluteAli = new URL(connection.getURL(), ali); URL absoluteAli = new URL(connection.getURL(), ali);
if (!urlEqualsIgnoreSlash(apiRoot, absoluteAli.toString())) { if (!urlEqualsIgnoreSlash(apiUrl, absoluteAli.toString())) {
// usually the URL that ALI points to is on the same host // usually the URL that ALI points to is on the same host
// so the TCP connection can be reused // so the TCP connection can be reused
@ -150,8 +150,8 @@ public final class AuthlibInjector {
} }
log(INFO, "Redirect to: " + absoluteAli); log(INFO, "Redirect to: " + absoluteAli);
apiRoot = absoluteAli.toString(); apiUrl = absoluteAli.toString();
warnIfHttp(apiRoot); warnIfHttp(apiUrl);
connection = (HttpURLConnection) absoluteAli.openConnection(); connection = (HttpURLConnection) absoluteAli.openConnection();
} }
} }
@ -161,37 +161,28 @@ public final class AuthlibInjector {
} }
} catch (IOException e) { } catch (IOException e) {
log(ERROR, "Failed to fetch metadata: " + e); log(ERROR, "Failed to fetch metadata: " + e);
throw new InjectorInitializationException(e); throw new InitializationException(e);
} }
} else {
log(DEBUG, "Prefetched metadata detected");
try {
metadataResponse = new String(Base64.getDecoder().decode(removeNewLines(prefetched.get())), UTF_8);
} catch (IllegalArgumentException e) {
log(ERROR, "Unable to decode metadata: " + e + "\n"
+ "Encoded metadata:\n"
+ prefetched.get());
throw new InjectorInitializationException(e);
}
} }
log(DEBUG, "Metadata: " + metadataResponse); log(DEBUG, "Metadata: " + metadataResponse);
if (!apiRoot.endsWith("/")) if (!apiUrl.endsWith("/")) {
apiRoot += "/"; apiUrl += "/";
}
YggdrasilConfiguration configuration; APIMetadata metadata;
try { try {
configuration = YggdrasilConfiguration.parse(apiRoot, metadataResponse); metadata = APIMetadata.parse(apiUrl, metadataResponse);
} catch (UncheckedIOException e) { } catch (UncheckedIOException e) {
log(ERROR, "Unable to parse metadata: " + e.getCause() + "\n" log(ERROR, "Unable to parse metadata: " + e.getCause() + "\n"
+ "Raw metadata:\n" + "Raw metadata:\n"
+ metadataResponse); + metadataResponse);
throw new InjectorInitializationException(e); throw new InitializationException(e);
} }
log(DEBUG, "Parsed metadata: " + configuration); log(DEBUG, "Parsed metadata: " + metadata);
return of(configuration); return metadata;
} }
private static void warnIfHttp(String url) { private static void warnIfHttp(String url) {
@ -216,7 +207,7 @@ public final class AuthlibInjector {
return a.equals(b); return a.equals(b);
} }
private static List<URLFilter> createFilters(YggdrasilConfiguration config) { private static List<URLFilter> createFilters(APIMetadata config) {
if (Config.httpdDisabled) { if (Config.httpdDisabled) {
return emptyList(); return emptyList();
} }
@ -238,7 +229,7 @@ public final class AuthlibInjector {
return filters; return filters;
} }
private static ClassTransformer createTransformer(YggdrasilConfiguration config) { private static ClassTransformer createTransformer(APIMetadata config) {
URLProcessor urlProcessor = new URLProcessor(createFilters(config), new DefaultURLRedirector(config)); URLProcessor urlProcessor = new URLProcessor(createFilters(config), new DefaultURLRedirector(config));
ClassTransformer transformer = new ClassTransformer(); ClassTransformer transformer = new ClassTransformer();
@ -265,10 +256,6 @@ public final class AuthlibInjector {
return transformer; return transformer;
} }
public static String getVersion() {
return AuthlibInjector.class.getPackage().getImplementationVersion();
}
public static void retransformClasses(String... classNames) { public static void retransformClasses(String... classNames) {
if (!retransformSupported) { if (!retransformSupported) {
return; return;

View file

@ -109,7 +109,7 @@ public final class Config {
break; break;
default: default:
log(ERROR, "Unrecognized debug option: " + option); log(ERROR, "Unrecognized debug option: " + option);
throw new InjectorInitializationException(); throw new InitializationException();
} }
} }
} }
@ -182,7 +182,7 @@ public final class Config {
Matcher matcher = Pattern.compile("^(?<protocol>[^:]+)://(?<host>[^/]+)+:(?<port>\\d+)$").matcher(prop); Matcher matcher = Pattern.compile("^(?<protocol>[^:]+)://(?<host>[^/]+)+:(?<port>\\d+)$").matcher(prop);
if (!matcher.find()) { if (!matcher.find()) {
log(ERROR, "Unrecognized proxy URL: " + prop); log(ERROR, "Unrecognized proxy URL: " + prop);
throw new InjectorInitializationException(); throw new InitializationException();
} }
String protocol = matcher.group("protocol"); String protocol = matcher.group("protocol");
@ -196,7 +196,7 @@ public final class Config {
default: default:
log(ERROR, "Unsupported proxy protocol: " + protocol); log(ERROR, "Unsupported proxy protocol: " + protocol);
throw new InjectorInitializationException(); throw new InitializationException();
} }
log(INFO, "Mojang proxy: " + mojangProxy); log(INFO, "Mojang proxy: " + mojangProxy);
} }

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2019 Haowei Wen <yushijinhun@gmail.com> and contributors * Copyright (C) 2020 Haowei Wen <yushijinhun@gmail.com> and contributors
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU Affero General Public License as published by
@ -16,12 +16,12 @@
*/ */
package moe.yushi.authlibinjector; package moe.yushi.authlibinjector;
public class InjectorInitializationException extends RuntimeException { public class InitializationException extends RuntimeException {
public InjectorInitializationException() { public InitializationException() {
} }
public InjectorInitializationException(Throwable cause) { public InitializationException(Throwable cause) {
super(cause); super(cause);
} }
} }

View file

@ -14,22 +14,21 @@
* You should have received a copy of the GNU Affero General Public License * You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package moe.yushi.authlibinjector.javaagent; package moe.yushi.authlibinjector;
import static moe.yushi.authlibinjector.util.Logging.log; import static moe.yushi.authlibinjector.util.Logging.log;
import static moe.yushi.authlibinjector.util.Logging.Level.DEBUG; import static moe.yushi.authlibinjector.util.Logging.Level.DEBUG;
import static moe.yushi.authlibinjector.util.Logging.Level.ERROR; import static moe.yushi.authlibinjector.util.Logging.Level.ERROR;
import static moe.yushi.authlibinjector.util.Logging.Level.INFO; import static moe.yushi.authlibinjector.util.Logging.Level.INFO;
import java.lang.instrument.Instrumentation; import java.lang.instrument.Instrumentation;
import moe.yushi.authlibinjector.AuthlibInjector;
import moe.yushi.authlibinjector.InjectorInitializationException;
public class AuthlibInjectorPremain { public final class Premain {
private Premain() {}
public static void premain(String arg, Instrumentation instrumentation) { public static void premain(String arg, Instrumentation instrumentation) {
try { try {
initInjector(arg, instrumentation, false); initInjector(arg, instrumentation, false);
} catch (InjectorInitializationException e) { } catch (InitializationException e) {
log(DEBUG, "A known exception has occurred", e); log(DEBUG, "A known exception has occurred", e);
System.exit(1); System.exit(1);
} catch (Throwable e) { } catch (Throwable e) {
@ -42,25 +41,18 @@ public class AuthlibInjectorPremain {
try { try {
log(INFO, "Launched from agentmain"); log(INFO, "Launched from agentmain");
initInjector(arg, instrumentation, true); initInjector(arg, instrumentation, true);
} catch (InjectorInitializationException e) { } catch (InitializationException e) {
log(DEBUG, "A known exception has occurred", e); log(DEBUG, "A known exception has occurred", e);
} catch (Throwable e) { } catch (Throwable e) {
log(ERROR, "An exception has occurred", e); log(ERROR, "An exception has occurred", e);
} }
} }
public static void initInjector(String arg, Instrumentation instrumentation, boolean retransform) { private static void initInjector(String arg, Instrumentation instrumentation, boolean retransform) {
setupConfig(arg); AuthlibInjector.bootstrap(instrumentation, arg);
AuthlibInjector.bootstrap(instrumentation);
if (retransform) { if (retransform) {
AuthlibInjector.retransformAllClasses(); AuthlibInjector.retransformAllClasses();
} }
} }
private static void setupConfig(String arg) {
if (arg != null && !arg.isEmpty()) {
System.setProperty(AuthlibInjector.PROP_API_ROOT, arg);
}
}
} }

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2019 Haowei Wen <yushijinhun@gmail.com> and contributors * Copyright (C) 2020 Haowei Wen <yushijinhun@gmail.com> and contributors
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU Affero General Public License as published by
@ -19,15 +19,14 @@ package moe.yushi.authlibinjector.httpd;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import moe.yushi.authlibinjector.APIMetadata;
import moe.yushi.authlibinjector.YggdrasilConfiguration;
public class DefaultURLRedirector implements URLRedirector { public class DefaultURLRedirector implements URLRedirector {
private Map<String, String> domainMapping = new HashMap<>(); private Map<String, String> domainMapping = new HashMap<>();
private String apiRoot; private String apiRoot;
public DefaultURLRedirector(YggdrasilConfiguration config) { public DefaultURLRedirector(APIMetadata config) {
initDomainMapping(); initDomainMapping();
apiRoot = config.getApiRoot(); apiRoot = config.getApiRoot();

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2019 Haowei Wen <yushijinhun@gmail.com> and contributors * Copyright (C) 2020 Haowei Wen <yushijinhun@gmail.com> and contributors
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU Affero General Public License as published by
@ -17,16 +17,14 @@
package moe.yushi.authlibinjector.yggdrasil; package moe.yushi.authlibinjector.yggdrasil;
import static moe.yushi.authlibinjector.util.UUIDUtils.toUnsignedUUID; import static moe.yushi.authlibinjector.util.UUIDUtils.toUnsignedUUID;
import java.util.UUID; import java.util.UUID;
import moe.yushi.authlibinjector.APIMetadata;
import moe.yushi.authlibinjector.YggdrasilConfiguration;
public class CustomYggdrasilAPIProvider implements YggdrasilAPIProvider { public class CustomYggdrasilAPIProvider implements YggdrasilAPIProvider {
private String apiRoot; private String apiRoot;
public CustomYggdrasilAPIProvider(YggdrasilConfiguration configuration) { public CustomYggdrasilAPIProvider(APIMetadata configuration) {
this.apiRoot = configuration.getApiRoot(); this.apiRoot = configuration.getApiRoot();
} }

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2019 Haowei Wen <yushijinhun@gmail.com> and contributors * Copyright (C) 2020 Haowei Wen <yushijinhun@gmail.com> and contributors
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU Affero General Public License as published by
@ -19,18 +19,15 @@ package moe.yushi.authlibinjector.test;
import static java.util.Collections.emptyList; import static java.util.Collections.emptyList;
import static java.util.Collections.emptyMap; import static java.util.Collections.emptyMap;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import java.util.Optional; import java.util.Optional;
import org.junit.Test; import org.junit.Test;
import moe.yushi.authlibinjector.APIMetadata;
import moe.yushi.authlibinjector.YggdrasilConfiguration;
import moe.yushi.authlibinjector.httpd.DefaultURLRedirector; import moe.yushi.authlibinjector.httpd.DefaultURLRedirector;
public class DefaultURLRedirectorTest { public class DefaultURLRedirectorTest {
private String apiRoot = "https://yggdrasil.example.com/"; private String apiRoot = "https://yggdrasil.example.com/";
private DefaultURLRedirector redirector = new DefaultURLRedirector(new YggdrasilConfiguration(apiRoot, emptyList(), emptyMap(), Optional.empty())); private DefaultURLRedirector redirector = new DefaultURLRedirector(new APIMetadata(apiRoot, emptyList(), emptyMap(), Optional.empty()));
private void testTransform(String domain, String path, String output) { private void testTransform(String domain, String path, String output) {
assertEquals(redirector.redirect(domain, path).get(), output); assertEquals(redirector.redirect(domain, path).get(), output);