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"