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/pom.xml b/pom.xml index edf4dcf..00ce7ca 100644 --- a/pom.xml +++ b/pom.xml @@ -37,10 +37,18 @@ 1.3.9 provided + com.nukkitx.protocol bedrock-v390 - 2.5.5 + 2.5.7-SNAPSHOT + compile + + + + com.nukkitx.protocol + bedrock-v363 + 2.5.7-SNAPSHOT compile diff --git a/src/main/java/com/nukkitx/proxypass/Configuration.java b/src/main/java/com/nukkitx/proxypass/Configuration.java index 9ed9ff2..f1660d3 100644 --- a/src/main/java/com/nukkitx/proxypass/Configuration.java +++ b/src/main/java/com/nukkitx/proxypass/Configuration.java @@ -27,6 +27,9 @@ public class Configuration { @JsonProperty("log-packets") private boolean loggingPackets = false; + @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 3da9c44..802377e 100644 --- a/src/main/java/com/nukkitx/proxypass/ProxyPass.java +++ b/src/main/java/com/nukkitx/proxypass/ProxyPass.java @@ -13,6 +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.v363.Bedrock_v363; import com.nukkitx.protocol.bedrock.v390.Bedrock_v390; import com.nukkitx.proxypass.network.ProxyBedrockEventHandler; import lombok.AccessLevel; @@ -37,8 +38,6 @@ 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 int PROTOCOL_VERSION = CODEC.getProtocolVersion(); private static final DefaultPrettyPrinter PRETTY_PRINTER = new DefaultPrettyPrinter(); static { @@ -56,6 +55,8 @@ public class ProxyPass { MINECRAFT_VERSION = minecraftVersion; } + public BedrockPacketCodec CODEC; + public int PROTOCOL_VERSION; private final AtomicBoolean running = new AtomicBoolean(true); private BedrockServer bedrockServer; private final Set clients = Collections.newSetFromMap(new ConcurrentHashMap<>()); @@ -86,6 +87,14 @@ public void boot() throws IOException { configuration = Configuration.load(configPath); + if (configuration.isEducation()) { + CODEC = Bedrock_v363.V363_CODEC; + } else { + CODEC = Bedrock_v390.V390_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..50d09ef 100644 --- a/src/main/java/com/nukkitx/proxypass/network/ProxyBedrockEventHandler.java +++ b/src/main/java/com/nukkitx/proxypass/network/ProxyBedrockEventHandler.java @@ -13,22 +13,28 @@ 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); + ADVERTISEMENT.setProtocolVersion(proxy.PROTOCOL_VERSION); ADVERTISEMENT.setMotd("ProxyPass"); 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 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/ProxyPlayerSession.java b/src/main/java/com/nukkitx/proxypass/network/bedrock/session/ProxyPlayerSession.java index 655e309..1c5d791 100644 --- a/src/main/java/com/nukkitx/proxypass/network/bedrock/session/ProxyPlayerSession.java +++ b/src/main/java/com/nukkitx/proxypass/network/bedrock/session/ProxyPlayerSession.java @@ -122,9 +122,9 @@ public void handle(BedrockSession session, ByteBuf compressed, Collection ProxyPass.PROTOCOL_VERSION) { + if (protocolVersion > proxy.PROTOCOL_VERSION) { status.setStatus(PlayStatusPacket.Status.FAILED_SERVER); } else { status.setStatus(PlayStatusPacket.Status.FAILED_CLIENT); } } - session.setPacketCodec(ProxyPass.CODEC); + session.setPacketCodec(proxy.CODEC); JsonNode certData; try { @@ -111,6 +120,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); @@ -131,7 +141,7 @@ private void initializeProxySession() { log.error("Unable to connect to downstream server " + proxy.getTargetAddress(), throwable); return; } - downstream.setPacketCodec(ProxyPass.CODEC); + downstream.setPacketCodec(proxy.CODEC); ProxyPlayerSession proxySession = new ProxyPlayerSession(this.session, downstream, this.proxy, this.authData); this.player = proxySession; @@ -150,7 +160,7 @@ private void initializeProxySession() { LoginPacket login = new LoginPacket(); login.setChainData(chainData); login.setSkinData(AsciiString.of(skinData.serialize())); - login.setProtocolVersion(ProxyPass.PROTOCOL_VERSION); + login.setProtocolVersion(proxy.PROTOCOL_VERSION); downstream.sendPacketImmediately(login); this.session.setBatchedHandler(proxySession.getUpstreamBatchHandler()); 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..4fcda63 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,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/java/com/nukkitx/proxypass/network/bedrock/util/RecipeUtils.java b/src/main/java/com/nukkitx/proxypass/network/bedrock/util/RecipeUtils.java index 154c337..fa3e8c6 100644 --- a/src/main/java/com/nukkitx/proxypass/network/bedrock/util/RecipeUtils.java +++ b/src/main/java/com/nukkitx/proxypass/network/bedrock/util/RecipeUtils.java @@ -95,7 +95,7 @@ public static void writeRecipes(CraftingDataPacket packet, ProxyPass proxy) { entries.add(entry); } - Recipes recipes = new Recipes(ProxyPass.CODEC.getProtocolVersion(), entries, packet.getPotionMixData(), packet.getContainerMixData()); + Recipes recipes = new Recipes(proxy.CODEC.getProtocolVersion(), entries, packet.getPotionMixData(), packet.getContainerMixData()); proxy.saveJson("recipes.json", recipes); } diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 990cf5e..c675c9b 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -11,6 +11,8 @@ destination: packet-testing: false ## Log packets for each session log-packets: true +## Minecraft Education Support +education: false ## Packets to ignore to make your log more refined. These default packet are generally spammed ignored-packets: