From d511b95415474cf8ba32878c71d2239b3e8e016f Mon Sep 17 00:00:00 2001 From: Brendan Grieve Date: Fri, 8 May 2020 19:07:51 +0800 Subject: [PATCH] Initial Support for Minecraft Education Edition * This uses my private build of protocol v363 which is really just v361 rebadged * Will proxy through the MCEE signedToken correctly and enable encryption to both ends --- pom.xml | 4 +-- .../java/com/nukkitx/proxypass/ProxyPass.java | 4 +-- .../network/ProxyBedrockEventHandler.java | 2 +- .../session/DownstreamPacketHandler.java | 12 +++++++++ .../session/UpstreamPacketHandler.java | 9 +++++++ .../network/bedrock/util/ForgeryUtils.java | 26 +++++++++++++++++++ 6 files changed, 52 insertions(+), 5 deletions(-) diff --git a/pom.xml b/pom.xml index edf4dcf..e2af623 100644 --- a/pom.xml +++ b/pom.xml @@ -39,8 +39,8 @@ com.nukkitx.protocol - bedrock-v390 - 2.5.5 + bedrock-v363 + 2.5.6 compile diff --git a/src/main/java/com/nukkitx/proxypass/ProxyPass.java b/src/main/java/com/nukkitx/proxypass/ProxyPass.java index 3da9c44..4929809 100644 --- a/src/main/java/com/nukkitx/proxypass/ProxyPass.java +++ b/src/main/java/com/nukkitx/proxypass/ProxyPass.java @@ -13,7 +13,7 @@ import com.nukkitx.protocol.bedrock.BedrockClient; import com.nukkitx.protocol.bedrock.BedrockPacketCodec; import com.nukkitx.protocol.bedrock.BedrockServer; -import com.nukkitx.protocol.bedrock.v390.Bedrock_v390; +import com.nukkitx.protocol.bedrock.v363.Bedrock_v363; import com.nukkitx.proxypass.network.ProxyBedrockEventHandler; import lombok.AccessLevel; import lombok.Getter; @@ -37,7 +37,7 @@ 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_v390.V390_CODEC; + public static final BedrockPacketCodec CODEC = Bedrock_v363.V363_CODEC; public static final int PROTOCOL_VERSION = CODEC.getProtocolVersion(); private static final DefaultPrettyPrinter PRETTY_PRINTER = new DefaultPrettyPrinter(); diff --git a/src/main/java/com/nukkitx/proxypass/network/ProxyBedrockEventHandler.java b/src/main/java/com/nukkitx/proxypass/network/ProxyBedrockEventHandler.java index e11611f..8be7183 100644 --- a/src/main/java/com/nukkitx/proxypass/network/ProxyBedrockEventHandler.java +++ b/src/main/java/com/nukkitx/proxypass/network/ProxyBedrockEventHandler.java @@ -21,7 +21,7 @@ public class ProxyBedrockEventHandler implements BedrockServerEventHandler { private final ProxyPass proxy; static { - ADVERTISEMENT.setEdition("MCPE"); + ADVERTISEMENT.setEdition("MCEE"); ADVERTISEMENT.setGameType("Survival"); ADVERTISEMENT.setVersion(ProxyPass.MINECRAFT_VERSION); ADVERTISEMENT.setProtocolVersion(ProxyPass.PROTOCOL_VERSION); 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 6f8aff1..a7d6259 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 @@ -15,6 +15,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; @@ -46,6 +47,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 231a147..b43ee02 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 @@ -11,12 +11,14 @@ import com.nimbusds.jwt.SignedJWT; 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; @@ -36,6 +38,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; @@ -63,6 +67,10 @@ 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(); @@ -111,6 +119,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); 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 bbedfac..b6989e0 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 @@ -71,4 +71,30 @@ 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 claimsSet = new JWTClaimsSet.Builder() + .claim("signedToken", signedToken) + .claim("salt", Base64.getEncoder().encodeToString(token)) + .build(); + + SignedJWT jwt = new SignedJWT(header, claimsSet); + + try { + EncryptionUtils.signJwt(jwt, (ECPrivateKey) pair.getPrivate()); + } catch (JOSEException e) { + throw new RuntimeException(e); + } + + return jwt; + } }