Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial Support for Minecraft Education Edition #10

Open
wants to merge 15 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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__
25 changes: 22 additions & 3 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,17 @@
<id>nukkitx-repo</id>
<url>https://repo.nukkitx.com/snapshot</url>
</repository>
<!-- Bundabrg's Repo (Whilst Protocol v363 is unmerged) -->
<repository>
<id>bundabrg-repo</id>
<url>https://repo.worldguard.com.au/repository/maven-public</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>

<properties>
Expand All @@ -37,10 +48,18 @@
<version>1.3.9</version>
<scope>provided</scope>
</dependency>
<!-- Current Bedrock Version -->
<dependency>
<groupId>au.com.grieve.reversion</groupId>
<artifactId>bedrock-v423</artifactId>
<version>1.1.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
<!-- Current Education Version -->
<dependency>
<groupId>com.nukkitx.protocol</groupId>
<artifactId>bedrock-v422</artifactId>
<version>2.6.1-SNAPSHOT</version>
<groupId>au.com.grieve.reversion</groupId>
<artifactId>education-v392</artifactId>
<version>1.1.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
<dependency>
Expand Down
3 changes: 3 additions & 0 deletions src/main/java/com/nukkitx/proxypass/Configuration.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ public class Configuration {
@JsonProperty("log-to")
private LogTo logTo = LogTo.FILE;

@JsonProperty("education")
private boolean education = false;

@JsonProperty("ignored-packets")
private Set<String> ignoredPackets = Collections.emptySet();

Expand Down
17 changes: 13 additions & 4 deletions src/main/java/com/nukkitx/proxypass/ProxyPass.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.nukkitx.proxypass;

import au.com.grieve.reversion.protocol.bedrock.v423.Bedrock_v423;
import au.com.grieve.reversion.protocol.education.v392.Education_v392;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.core.util.DefaultIndenter;
import com.fasterxml.jackson.core.util.DefaultPrettyPrinter;
Expand All @@ -13,7 +15,6 @@
import com.nukkitx.protocol.bedrock.BedrockClient;
import com.nukkitx.protocol.bedrock.BedrockPacketCodec;
import com.nukkitx.protocol.bedrock.BedrockServer;
import com.nukkitx.protocol.bedrock.v422.Bedrock_v422;
import com.nukkitx.proxypass.network.ProxyBedrockEventHandler;
import io.netty.util.ResourceLeakDetector;
import lombok.AccessLevel;
Expand All @@ -39,8 +40,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_v422.V422_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 {
Expand Down Expand Up @@ -89,6 +90,14 @@ public void boot() throws IOException {

configuration = Configuration.load(configPath);

if (configuration.isEducation()) {
CODEC = Education_v392.V392_CODEC;
} else {
CODEC = Bedrock_v423.V423_CODEC;
}

PROTOCOL_VERSION = CODEC.getProtocolVersion();

proxyAddress = configuration.getProxy().getAddress();
targetAddress = configuration.getDestination().getAddress();
maxClients = configuration.getMaxClients();
Expand Down Expand Up @@ -199,7 +208,7 @@ public void saveMojangson(String name, NbtMap nbt) {
public boolean isIgnoredPacket(Class<?> clazz) {
return this.ignoredPackets.contains(clazz);
}

public boolean isFull() {
return maxClients > 0 ? this.clients.size() >= maxClients : false;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,26 +15,30 @@
@Log4j2
@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.setMotd("ProxyPass");
ADVERTISEMENT.setPlayerCount(0);
ADVERTISEMENT.setMaximumPlayerCount(20);
ADVERTISEMENT.setSubMotd("https://github.com/NukkitX/ProxyPass");
}

public ProxyBedrockEventHandler(ProxyPass proxy) {
this.proxy = proxy;
int port = this.proxy.getProxyAddress().getPort();
ADVERTISEMENT.setIpv4Port(port);
ADVERTISEMENT.setIpv6Port(port);

}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import com.nukkitx.protocol.bedrock.packet.*;
import com.nukkitx.protocol.bedrock.util.EncryptionUtils;
import com.nukkitx.proxypass.ProxyPass;
import com.nukkitx.proxypass.network.bedrock.util.ForgeryUtils;
import com.nukkitx.proxypass.network.bedrock.util.RecipeUtils;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
Expand Down Expand Up @@ -49,6 +50,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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,8 @@ public void handle(BedrockSession session, ByteBuf compressed, Collection<Bedroc
packet, packet2);
}
} catch (PacketSerializeException e) {
//ignore
log.warn("An exception occurred trying to test packet:\n {}", packet);
e.printStackTrace();
} finally {
buffer.release();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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);
Expand All @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
5 changes: 5 additions & 0 deletions src/main/resources/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ log-packets: true
## Where to log packet data
## Valid options: console, file or both
log-to: file
## Minecraft Education Support
education: false

## Minecraft Education Support
education: false

## Packets to ignore to make your log more refined. These default packet are generally spammed
ignored-packets:
Expand Down