From a91c8420abc493225835c3ff6aad12b33017366b Mon Sep 17 00:00:00 2001 From: bundabrg Date: Tue, 12 May 2020 19:49:09 +0800 Subject: [PATCH] Implement Minecraft Education v1.12.60 support Changes: * Support both MCEE and MCPE versions. * Config entry to enable/disable education support * Enable 2 way encryption by default now whilst still performing mitm * Update for 1.16 * Set raknetProtocol Version from codec --- .gitignore | 4 ++- README.md | 2 +- pom.xml | 8 ++++++ .../com/nukkitx/proxypass/Configuration.java | 3 ++ .../java/com/nukkitx/proxypass/ProxyPass.java | 13 +++++++-- .../network/ProxyBedrockEventHandler.java | 14 +++++++--- .../session/DownstreamPacketHandler.java | 12 ++++++++ .../session/UpstreamPacketHandler.java | 12 +++++++- .../network/bedrock/util/ForgeryUtils.java | 28 +++++++++++++++++++ src/main/resources/config.yml | 3 ++ 10 files changed, 90 insertions(+), 9 deletions(-) diff --git a/.gitignore b/.gitignore index 68dda6d..f25b734 100644 --- a/.gitignore +++ b/.gitignore @@ -205,4 +205,6 @@ config.yml logs/ logs/* packet-logs/ -packet-logs/* \ No newline at end of file +packet-logs/* +data/* +sessions/* \ No newline at end of file diff --git a/README.md b/README.md index b86270c..9f637c0 100644 --- a/README.md +++ b/README.md @@ -11,4 +11,4 @@ __ProxyPass requires Java 8 u162 or later to function correctly due to the encr __[Jenkins](https://ci.nukkitx.com/job/NukkitX/job/ProxyPass/job/master/)__ -__[Protocol library](https://github.com/NukkitX/Protocol) used in this project__ +__[Protocol library](https://github.com/NukkitX/Protocol) used in this project__ \ No newline at end of file diff --git a/pom.xml b/pom.xml index 0251d97..82244a7 100644 --- a/pom.xml +++ b/pom.xml @@ -37,12 +37,20 @@ 1.3.9 provided + com.nukkitx.protocol bedrock-v407 2.6.0-SNAPSHOT compile + + + com.nukkitx.protocol + bedrock-v363 + 2.6.0-SNAPSHOT + compile + com.fasterxml.jackson.core jackson-databind diff --git a/src/main/java/com/nukkitx/proxypass/Configuration.java b/src/main/java/com/nukkitx/proxypass/Configuration.java index 5673272..a4d26ab 100644 --- a/src/main/java/com/nukkitx/proxypass/Configuration.java +++ b/src/main/java/com/nukkitx/proxypass/Configuration.java @@ -30,6 +30,9 @@ public class Configuration { @JsonProperty("log-to") private LogTo logTo = LogTo.FILE; + @JsonProperty("education") + private boolean education = false; + @JsonProperty("ignored-packets") private Set ignoredPackets = Collections.emptySet(); diff --git a/src/main/java/com/nukkitx/proxypass/ProxyPass.java b/src/main/java/com/nukkitx/proxypass/ProxyPass.java index ffe7cef..7d47d33 100644 --- a/src/main/java/com/nukkitx/proxypass/ProxyPass.java +++ b/src/main/java/com/nukkitx/proxypass/ProxyPass.java @@ -12,6 +12,7 @@ import com.nukkitx.protocol.bedrock.BedrockClient; import com.nukkitx.protocol.bedrock.BedrockPacketCodec; import com.nukkitx.protocol.bedrock.BedrockServer; +import com.nukkitx.protocol.bedrock.v363.Bedrock_v363; import com.nukkitx.protocol.bedrock.v407.Bedrock_v407; import com.nukkitx.proxypass.network.ProxyBedrockEventHandler; import io.netty.util.ResourceLeakDetector; @@ -37,8 +38,8 @@ public class ProxyPass { public static final ObjectMapper JSON_MAPPER = new ObjectMapper().disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); public static final YAMLMapper YAML_MAPPER = (YAMLMapper) new YAMLMapper().disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); public static final String MINECRAFT_VERSION; - public static final BedrockPacketCodec CODEC = Bedrock_v407.V407_CODEC; - public static final int PROTOCOL_VERSION = CODEC.getProtocolVersion(); + public static BedrockPacketCodec CODEC; + public static int PROTOCOL_VERSION; private static final DefaultPrettyPrinter PRETTY_PRINTER = new DefaultPrettyPrinter(); static { @@ -87,6 +88,14 @@ public void boot() throws IOException { configuration = Configuration.load(configPath); + if (configuration.isEducation()) { + CODEC = Bedrock_v363.V363_CODEC; + } else { + CODEC = Bedrock_v407.V407_CODEC; + } + + PROTOCOL_VERSION = CODEC.getProtocolVersion(); + proxyAddress = configuration.getProxy().getAddress(); targetAddress = configuration.getDestination().getAddress(); diff --git a/src/main/java/com/nukkitx/proxypass/network/ProxyBedrockEventHandler.java b/src/main/java/com/nukkitx/proxypass/network/ProxyBedrockEventHandler.java index e11611f..ea54585 100644 --- a/src/main/java/com/nukkitx/proxypass/network/ProxyBedrockEventHandler.java +++ b/src/main/java/com/nukkitx/proxypass/network/ProxyBedrockEventHandler.java @@ -13,15 +13,20 @@ import java.net.InetSocketAddress; @Log4j2 -@RequiredArgsConstructor @ParametersAreNonnullByDefault public class ProxyBedrockEventHandler implements BedrockServerEventHandler { - private static final BedrockPong ADVERTISEMENT = new BedrockPong(); private final ProxyPass proxy; + private final BedrockPong ADVERTISEMENT = new BedrockPong(); - static { - ADVERTISEMENT.setEdition("MCPE"); + public ProxyBedrockEventHandler(ProxyPass proxy) { + this.proxy = proxy; + + if (proxy.getConfiguration().isEducation()) { + ADVERTISEMENT.setEdition("MCEE"); + } else { + ADVERTISEMENT.setEdition("MCPE"); + } ADVERTISEMENT.setGameType("Survival"); ADVERTISEMENT.setVersion(ProxyPass.MINECRAFT_VERSION); ADVERTISEMENT.setProtocolVersion(ProxyPass.PROTOCOL_VERSION); @@ -29,6 +34,7 @@ public class ProxyBedrockEventHandler implements BedrockServerEventHandler { ADVERTISEMENT.setPlayerCount(0); ADVERTISEMENT.setMaximumPlayerCount(20); ADVERTISEMENT.setSubMotd("https://github.com/NukkitX/ProxyPass"); + } @Override diff --git a/src/main/java/com/nukkitx/proxypass/network/bedrock/session/DownstreamPacketHandler.java b/src/main/java/com/nukkitx/proxypass/network/bedrock/session/DownstreamPacketHandler.java index 727024b..dffd66b 100644 --- a/src/main/java/com/nukkitx/proxypass/network/bedrock/session/DownstreamPacketHandler.java +++ b/src/main/java/com/nukkitx/proxypass/network/bedrock/session/DownstreamPacketHandler.java @@ -16,6 +16,7 @@ import com.nukkitx.protocol.bedrock.util.EncryptionUtils; import com.nukkitx.proxypass.ProxyPass; import com.nukkitx.proxypass.network.bedrock.util.BlockPaletteUtils; +import com.nukkitx.proxypass.network.bedrock.util.ForgeryUtils; import com.nukkitx.proxypass.network.bedrock.util.RecipeUtils; import lombok.RequiredArgsConstructor; import lombok.Value; @@ -47,6 +48,17 @@ public boolean handle(ServerToClientHandshakePacket packet) { SecretKey key = EncryptionUtils.getSecretKey(this.player.getProxyKeyPair().getPrivate(), serverKey, Base64.getDecoder().decode(saltJwt.getJWTClaimsSet().getStringClaim("salt"))); session.enableEncryption(key); + + ServerToClientHandshakePacket p = new ServerToClientHandshakePacket(); + p.setJwt(ForgeryUtils.forgeHandshake( + player.getProxyKeyPair(), + saltJwt.getJWTClaimsSet().getStringClaim("signedToken"), + Base64.getDecoder().decode(saltJwt.getJWTClaimsSet().getStringClaim("salt"))).serialize() + ); + player.getUpstream().sendPacketImmediately(p); + player.getUpstream().enableEncryption(EncryptionUtils.getSecretKey(player.getProxyKeyPair().getPrivate(), + ((UpstreamPacketHandler)player.getUpstream().getPacketHandler()).getRemotePublicKey(),Base64.getDecoder().decode(saltJwt.getJWTClaimsSet().getStringClaim("salt")))); + } catch (ParseException | NoSuchAlgorithmException | InvalidKeySpecException | InvalidKeyException e) { throw new RuntimeException(e); } diff --git a/src/main/java/com/nukkitx/proxypass/network/bedrock/session/UpstreamPacketHandler.java b/src/main/java/com/nukkitx/proxypass/network/bedrock/session/UpstreamPacketHandler.java index 58ad902..c7e442e 100644 --- a/src/main/java/com/nukkitx/proxypass/network/bedrock/session/UpstreamPacketHandler.java +++ b/src/main/java/com/nukkitx/proxypass/network/bedrock/session/UpstreamPacketHandler.java @@ -12,12 +12,14 @@ import com.nukkitx.protocol.bedrock.BedrockClient; import com.nukkitx.protocol.bedrock.BedrockServerSession; import com.nukkitx.protocol.bedrock.handler.BedrockPacketHandler; +import com.nukkitx.protocol.bedrock.packet.ClientToServerHandshakePacket; import com.nukkitx.protocol.bedrock.packet.LoginPacket; import com.nukkitx.protocol.bedrock.packet.PlayStatusPacket; import com.nukkitx.protocol.bedrock.util.EncryptionUtils; import com.nukkitx.proxypass.ProxyPass; import com.nukkitx.proxypass.network.bedrock.util.ForgeryUtils; import io.netty.util.AsciiString; +import lombok.Getter; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import net.minidev.json.JSONObject; @@ -37,6 +39,8 @@ public class UpstreamPacketHandler implements BedrockPacketHandler { private ArrayNode chainData; private AuthData authData; private ProxyPlayerSession player; + @Getter + private ECPublicKey remotePublicKey; private static boolean validateChainData(JsonNode data) throws Exception { ECPublicKey lastKey = null; @@ -64,6 +68,11 @@ private static boolean verifyJwt(JWSObject jwt, ECPublicKey key) throws JOSEExce return jwt.verify(new DefaultJWSVerifierFactory().createJWSVerifier(jwt.getHeader(), key)); } + public boolean handle(ClientToServerHandshakePacket packet) { + // This is handled ourselves and we don't want a duplicate packet + return true; + } + @Override public boolean handle(LoginPacket packet) { int protocolVersion = packet.getProtocolVersion(); @@ -112,6 +121,7 @@ public boolean handle(LoginPacket packet) { throw new RuntimeException("Identity Public Key was not found!"); } ECPublicKey identityPublicKey = EncryptionUtils.generateKey(payload.get("identityPublicKey").textValue()); + this.remotePublicKey = identityPublicKey; JWSObject clientJwt = JWSObject.parse(packet.getSkinData().toString()); verifyJwt(clientJwt, identityPublicKey); @@ -128,7 +138,7 @@ public boolean handle(LoginPacket packet) { private void initializeProxySession() { log.debug("Initializing proxy session"); BedrockClient client = proxy.newClient(); - client.setRakNetVersion(10); + client.setRakNetVersion(ProxyPass.CODEC.getRaknetProtocolVersion()); client.connect(proxy.getTargetAddress()).whenComplete((downstream, throwable) -> { if (throwable != null) { log.error("Unable to connect to downstream server " + proxy.getTargetAddress(), throwable); diff --git a/src/main/java/com/nukkitx/proxypass/network/bedrock/util/ForgeryUtils.java b/src/main/java/com/nukkitx/proxypass/network/bedrock/util/ForgeryUtils.java index 9a45233..5603012 100644 --- a/src/main/java/com/nukkitx/proxypass/network/bedrock/util/ForgeryUtils.java +++ b/src/main/java/com/nukkitx/proxypass/network/bedrock/util/ForgeryUtils.java @@ -63,4 +63,32 @@ public static JWSObject forgeSkinData(KeyPair pair, JSONObject skinData) { return jws; } + + public static SignedJWT forgeHandshake(KeyPair pair, String signedToken, byte[] token) { + String publicKeyBase64 = Base64.getEncoder().encodeToString(pair.getPublic().getEncoded()); + URI x5u = URI.create(publicKeyBase64); + + JWSHeader header = new JWSHeader.Builder(JWSAlgorithm.ES384).x509CertURL(x5u).build(); + + long timestamp = System.currentTimeMillis(); + Date nbf = new Date(timestamp - TimeUnit.SECONDS.toMillis(1)); + Date exp = new Date(timestamp + TimeUnit.DAYS.toMillis(1)); + + JWTClaimsSet.Builder claimsBuilder = new JWTClaimsSet.Builder() + .claim("salt", Base64.getEncoder().encodeToString(token)); + + if (signedToken != null) { + claimsBuilder.claim("signedToken", signedToken); + } + + SignedJWT jwt = new SignedJWT(header, claimsBuilder.build()); + + try { + EncryptionUtils.signJwt(jwt, (ECPrivateKey) pair.getPrivate()); + } catch (JOSEException e) { + throw new RuntimeException(e); + } + + return jwt; + } } diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index dcde463..f79ff99 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -15,6 +15,9 @@ log-packets: true ## Valid options: console, file or both log-to: file +## Minecraft Education Support +education: false + ## Packets to ignore to make your log more refined. These default packet are generally spammed ignored-packets: - "NetworkStackLatencyPacket"