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;
|
||||
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.net.Socket;
|
||||
import javax.crypto.SecretKey;
|
||||
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.PacketInputStream;
|
||||
import org.bouncycastle.crypto.io.CipherInputStream;
|
||||
import org.bouncycastle.crypto.io.CipherOutputStream;
|
||||
|
||||
public class InitialHandler implements Runnable {
|
||||
|
||||
private final Socket socket;
|
||||
private final PacketInputStream in;
|
||||
private final OutputStream out;
|
||||
private PacketInputStream in;
|
||||
private OutputStream out;
|
||||
|
||||
public InitialHandler(Socket socket) throws IOException {
|
||||
this.socket = socket;
|
||||
@ -27,6 +33,24 @@ public class InitialHandler implements Runnable {
|
||||
switch (id) {
|
||||
case 0x02:
|
||||
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;
|
||||
case 0xFE:
|
||||
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();
|
||||
this.id = id;
|
||||
writeByte(id);
|
||||
packet = null;
|
||||
}
|
||||
|
||||
public byte[] getPacket() {
|
||||
|
Loading…
Reference in New Issue
Block a user