Add client -> proxy connections, encryption and mojang authentication.
This commit is contained in:
parent
b876fb2e1b
commit
e4cf00280d
89
src/main/java/net/md_5/bungee/EncryptionUtil.java
Normal file
89
src/main/java/net/md_5/bungee/EncryptionUtil.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
@ -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() {
|
||||||
|
Loading…
Reference in New Issue
Block a user