Add client -> proxy connections, encryption and mojang authentication.

This commit is contained in:
md_5 2012-10-05 11:52:57 +10:00
parent b876fb2e1b
commit e4cf00280d
3 changed files with 115 additions and 3 deletions

View File

@ -0,0 +1,89 @@
package net.md_5.bungee;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.math.BigInteger;
import java.net.URL;
import java.net.URLEncoder;
import java.security.Key;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.Security;
import java.util.Arrays;
import java.util.Random;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import net.md_5.bungee.packet.PacketFCEncryptionResponse;
import net.md_5.bungee.packet.PacketFDEncryptionRequest;
import org.bouncycastle.crypto.BufferedBlockCipher;
import org.bouncycastle.crypto.engines.AESFastEngine;
import org.bouncycastle.crypto.modes.CFBBlockCipher;
import org.bouncycastle.crypto.params.KeyParameter;
import org.bouncycastle.crypto.params.ParametersWithIV;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
public class EncryptionUtil {
private static final Random random = new Random();
private static KeyPair keys;
static {
Security.addProvider(new BouncyCastleProvider());
}
public static PacketFDEncryptionRequest encryptRequest() throws NoSuchAlgorithmException {
if (keys == null) {
keys = KeyPairGenerator.getInstance("RSA").generateKeyPair();
}
String hash = Long.toString(random.nextLong(), 16);
byte[] pubKey = keys.getPublic().getEncoded();
byte[] verify = new byte[4];
random.nextBytes(verify);
return new PacketFDEncryptionRequest(hash, pubKey, verify);
}
public static SecretKey getSecret(PacketFCEncryptionResponse resp, PacketFDEncryptionRequest request) throws Exception {
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, keys.getPrivate());
byte[] decrypted = cipher.doFinal(resp.verifyToken);
if (!Arrays.equals(request.verifyToken, decrypted)) {
throw new IllegalStateException("Key pairs do not match!");
}
cipher.init(Cipher.DECRYPT_MODE, keys.getPrivate());
byte[] shared = resp.sharedSecret;
byte[] secret = cipher.doFinal(shared);
return new SecretKeySpec(secret, "AES");
}
public static boolean isAuthenticated(String username, String connectionHash, SecretKey shared) throws NoSuchAlgorithmException, IOException {
String encName = URLEncoder.encode(username, "UTF-8");
MessageDigest sha = MessageDigest.getInstance("SHA-1");
for (byte[] bit : new byte[][]{connectionHash.getBytes("ISO_8859_1"), shared.getEncoded(), keys.getPublic().getEncoded()}) {
sha.update(bit);
}
String encodedHash = URLEncoder.encode(new BigInteger(sha.digest()).toString(16), "UTF-8");
String authURL = "http://session.minecraft.net/game/checkserver.jsp?user=" + encName + "&serverId=" + encodedHash;
String reply;
try (BufferedReader in = new BufferedReader(new InputStreamReader(new URL(authURL).openStream()))) {
reply = in.readLine();
}
return "YES".equals(reply);
}
public static BufferedBlockCipher getCipher(boolean forEncryption, Key shared) {
BufferedBlockCipher cip = new BufferedBlockCipher(new CFBBlockCipher(new AESFastEngine(), 8));
cip.init(forEncryption, new ParametersWithIV(new KeyParameter(shared.getEncoded()), shared.getEncoded(), 0, 16));
return cip;
}
}

View File

@ -1,17 +1,23 @@
package net.md_5.bungee; package net.md_5.bungee;
import java.io.BufferedOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
import java.net.Socket; import java.net.Socket;
import javax.crypto.SecretKey;
import net.md_5.bungee.packet.Packet2Handshake; import net.md_5.bungee.packet.Packet2Handshake;
import net.md_5.bungee.packet.PacketFCEncryptionResponse;
import net.md_5.bungee.packet.PacketFDEncryptionRequest;
import net.md_5.bungee.packet.PacketFFKick; import net.md_5.bungee.packet.PacketFFKick;
import net.md_5.bungee.packet.PacketInputStream; import net.md_5.bungee.packet.PacketInputStream;
import org.bouncycastle.crypto.io.CipherInputStream;
import org.bouncycastle.crypto.io.CipherOutputStream;
public class InitialHandler implements Runnable { public class InitialHandler implements Runnable {
private final Socket socket; private final Socket socket;
private final PacketInputStream in; private PacketInputStream in;
private final OutputStream out; private OutputStream out;
public InitialHandler(Socket socket) throws IOException { public InitialHandler(Socket socket) throws IOException {
this.socket = socket; this.socket = socket;
@ -27,6 +33,24 @@ public class InitialHandler implements Runnable {
switch (id) { switch (id) {
case 0x02: case 0x02:
Packet2Handshake handshake = new Packet2Handshake(packet); Packet2Handshake handshake = new Packet2Handshake(packet);
PacketFDEncryptionRequest request = EncryptionUtil.encryptRequest();
out.write(request.getPacket());
PacketFCEncryptionResponse response = new PacketFCEncryptionResponse(in.readPacket());
SecretKey shared = EncryptionUtil.getSecret(response, request);
if (!EncryptionUtil.isAuthenticated(handshake.username, request.serverId, shared)) {
throw new KickException("Not authenticated with minecraft.net");
}
out.write(new PacketFCEncryptionResponse().getPacket());
in = new PacketInputStream(new CipherInputStream(socket.getInputStream(), EncryptionUtil.getCipher(false, shared)));
out = new BufferedOutputStream(new CipherOutputStream(socket.getOutputStream(), EncryptionUtil.getCipher(true, shared)), 5120);
int ciphId = Util.getId(in.readPacket());
if (ciphId != 0xCD) {
throw new KickException("Unable to receive encrypted client status");
}
break; break;
case 0xFE: case 0xFE:
throw new KickException(BungeeCord.instance.config.motd + ChatColor.COLOR_CHAR + BungeeCord.instance.getOnlinePlayers() + ChatColor.COLOR_CHAR + BungeeCord.instance.config.maxPlayers); throw new KickException(BungeeCord.instance.config.motd + ChatColor.COLOR_CHAR + BungeeCord.instance.getOnlinePlayers() + ChatColor.COLOR_CHAR + BungeeCord.instance.config.maxPlayers);

View File

@ -42,7 +42,6 @@ public abstract class DefinedPacket implements DataInput, DataOutput {
out = ByteStreams.newDataOutput(); out = ByteStreams.newDataOutput();
this.id = id; this.id = id;
writeByte(id); writeByte(id);
packet = null;
} }
public byte[] getPacket() { public byte[] getPacket() {