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: