We're now up to the point where we need to work with EdDSA. More to come

This commit is contained in:
Manevolent
2018-07-24 16:23:10 -04:00
parent 24e6e366e5
commit 4a5bdcd03f
22 changed files with 655 additions and 166 deletions
@@ -1,20 +0,0 @@
package com.github.manevolent.ts3j.command;
import com.github.manevolent.ts3j.command.type.client.*;
import com.github.manevolent.ts3j.command.type.server.*;
import java.util.LinkedHashMap;
import java.util.Map;
public final class Commands {
private static final Map<String, Class<? extends TypedCommand>> commands = new LinkedHashMap<>();
static {
// Client
commands.put("clientinitiv", ClientInitIV.class);
// Server
commands.put("initivexpand", ServerInitIVExpand.class);
commands.put("initivexpand2", ServerInitIVExpand2.class);
}
}
@@ -1,20 +1,30 @@
package com.github.manevolent.ts3j.command;
import com.github.manevolent.ts3j.command.part.CommandOption;
import com.github.manevolent.ts3j.command.part.CommandParameter;
import com.github.manevolent.ts3j.command.part.CommandSingleParameter;
import com.github.manevolent.ts3j.protocol.ProtocolRole;
import com.github.manevolent.ts3j.util.Ts3Logging;
import com.github.manevolent.ts3j.util.Ts3String;
import java.util.Collection;
import java.util.Collections;
import java.util.*;
public class SimpleCommand implements Command {
private final String name;
private final ProtocolRole role;
private final Collection<CommandParameter> params;
private final Map<String, CommandParameter> params;
public SimpleCommand(String name, ProtocolRole role, Collection<CommandParameter> params) {
public SimpleCommand(String name, ProtocolRole role, CommandParameter... params) {
this(name, role, Arrays.asList(params));
}
public SimpleCommand(String name, ProtocolRole role, List<CommandParameter> params) {
this.name = name;
this.role = role;
this.params = params;
this.params = new LinkedHashMap<>();
for (CommandParameter parameter : params) add(parameter);
}
@Override
@@ -29,6 +39,58 @@ public class SimpleCommand implements Command {
@Override
public Iterable<CommandParameter> getParameters() {
return Collections.unmodifiableCollection(params);
return Collections.unmodifiableCollection(params.values());
}
public boolean has(String key) {
return params.containsKey(key.toLowerCase());
}
public void add(CommandParameter parameter) {
CommandParameter existing = get(parameter.getName());
if (existing != null) throw new IllegalArgumentException(parameter.getName() + " already set");
if (parameter.getName().trim().length() <= 0)
throw new IllegalArgumentException("invalid key (empty)");
this.params.put(parameter.getName().toLowerCase(), parameter);
}
public CommandParameter get(String key) {
return this.params.get(key.toLowerCase());
}
public CommandParameter remove(String key) {
return this.params.remove(key);
}
public static SimpleCommand parse(ProtocolRole role, String commandText) {
String[] commandParts = commandText.split(" ");
String commandLabel = commandParts[0].toLowerCase();
List<CommandParameter> parameterList = new LinkedList<>();
for (int i = 1; i < commandParts.length; i ++) {
String commandParamText = commandParts[i].trim();
if (commandParamText.length() <= 0) continue;
if (commandParamText.startsWith("-")) { // Option
parameterList.add(new CommandOption(commandParamText.substring(1)));
} else {
String[] commandParamParts = commandParamText.split("=", 2);
Ts3Logging.debug(commandParamText);
parameterList.add(new CommandSingleParameter(
commandParamParts[0].toLowerCase(),
commandParamParts.length > 1 ?
Ts3String.unescape(commandParamParts[1]) :
null
)
);
}
}
return new SimpleCommand(commandLabel, role, parameterList);
}
}
@@ -1,60 +0,0 @@
package com.github.manevolent.ts3j.command;
import com.github.manevolent.ts3j.command.part.CommandParameter;
import com.github.manevolent.ts3j.protocol.ProtocolRole;
import com.github.manevolent.ts3j.protocol.client.TeamspeakClient;
import com.github.manevolent.ts3j.protocol.server.TeamspeakServer;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
public abstract class TypedCommand implements Command {
private final ProtocolRole role;
private final String name;
private final Map<String, CommandParameter> parameters = new LinkedHashMap<>();
protected TypedCommand(ProtocolRole role, String name) {
this.role = role;
this.name = name;
}
protected CommandParameter set(CommandParameter commandParameter) {
this.parameters.put(commandParameter.getName(), commandParameter);
return commandParameter;
}
public CommandParameter get(String key) {
return this.parameters.get(key);
}
@Override
public Iterable<CommandParameter> getParameters() {
return Collections.unmodifiableCollection(parameters.values());
}
@Override
public String getName() {
return name;
}
@Override
public ProtocolRole getRole() {
return role;
}
@Override
public String toString() {
return build();
}
public void handleServer(TeamspeakServer server) {
}
public void handleClient(TeamspeakClient client) {
}
}
@@ -58,6 +58,11 @@ public class CommandOption implements CommandParameter {
throw new UnsupportedOperationException();
}
@Override
public String getValue() {
return enabled ? "true" : "false";
}
@Override
public String toString() {
if (enabled)
@@ -1,5 +1,7 @@
package com.github.manevolent.ts3j.command.part;
import java.util.Base64;
public interface CommandParameter {
String getName();
@@ -14,6 +16,12 @@ public interface CommandParameter {
void set(float value);
void set(double value);
default void set(byte[] value) {
set(Base64.getEncoder().encodeToString(value));
}
String getValue();
/**
* Converts the command part to a usable command string element.
* @return Command string element.
@@ -10,6 +10,11 @@ public class CommandSingleParameter implements CommandParameter {
this.key = key;
}
public CommandSingleParameter(String key, String value) {
this.key = key;
this.value = value;
}
@Override
public String toString() {
if (value == null)
@@ -67,4 +72,9 @@ public class CommandSingleParameter implements CommandParameter {
public void set(double value) {
this.value = Double.toString(value);
}
@Override
public String getValue() {
return value;
}
}
@@ -1,16 +0,0 @@
package com.github.manevolent.ts3j.command.type.client;
import com.github.manevolent.ts3j.command.TypedCommand;
import com.github.manevolent.ts3j.command.part.CommandSingleParameter;
import com.github.manevolent.ts3j.protocol.ProtocolRole;
public class ClientInitIV extends TypedCommand {
public ClientInitIV() {
super(ProtocolRole.CLIENT, "clientinitiv");
set(new CommandSingleParameter("ot"));
set(new CommandSingleParameter("alpha"));
set(new CommandSingleParameter("omega"));
set(new CommandSingleParameter("ip"));
}
}
@@ -1,15 +0,0 @@
package com.github.manevolent.ts3j.command.type.server;
import com.github.manevolent.ts3j.command.TypedCommand;
import com.github.manevolent.ts3j.command.part.CommandSingleParameter;
import com.github.manevolent.ts3j.protocol.ProtocolRole;
public class ServerInitIVExpand extends TypedCommand {
public ServerInitIVExpand() {
super(ProtocolRole.CLIENT, "initivexpand");
set(new CommandSingleParameter("alpha"));
set(new CommandSingleParameter("beta"));
set(new CommandSingleParameter("omega"));
}
}
@@ -1,18 +0,0 @@
package com.github.manevolent.ts3j.command.type.server;
import com.github.manevolent.ts3j.command.TypedCommand;
import com.github.manevolent.ts3j.command.part.CommandSingleParameter;
import com.github.manevolent.ts3j.protocol.ProtocolRole;
public class ServerInitIVExpand2 extends TypedCommand {
public ServerInitIVExpand2() {
super(ProtocolRole.CLIENT, "initivexpand2");
set(new CommandSingleParameter("l"));
set(new CommandSingleParameter("beta"));
set(new CommandSingleParameter("omega"));
set(new CommandSingleParameter("ot"));
set(new CommandSingleParameter("proof"));
set(new CommandSingleParameter("tvd"));
}
}
@@ -1,5 +1,6 @@
package com.github.manevolent.ts3j.identity;
import com.github.manevolent.ts3j.util.Ts3Crypt;
import com.github.manevolent.ts3j.util.Ts3Logging;
import org.bouncycastle.asn1.ASN1Encodable;
import org.bouncycastle.asn1.DERBitString;
@@ -34,7 +35,7 @@ public class Identity {
public Identity(ECPoint publicKey) {
this.publicKey = publicKey;
this.publicKeyString = Base64.getEncoder().encodeToString(encodePublicKey());
this.publicKeyString = Base64.getEncoder().encodeToString(Ts3Crypt.encodePublicKey(publicKey));
this.fingerprint = generateFingerprint();
}
@@ -136,24 +137,9 @@ public class Identity {
return curr;
}
private byte[] encodePublicKey() {
try {
byte[] dataArray = new DERSequence(new ASN1Encodable[]{
new DERBitString(new byte[]{0b0000_0000}, 7),
new DERInteger(32),
new DERInteger(publicKey.getAffineXCoord().toBigInteger()),
new DERInteger(publicKey.getAffineYCoord().toBigInteger())
}).getEncoded();
return dataArray;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private String generateFingerprint() {
synchronized (sha1) {
return Base64.getEncoder().encodeToString(sha1.digest(encodePublicKey()));
return Base64.getEncoder().encodeToString(sha1.digest(Ts3Crypt.encodePublicKey(publicKey)));
}
}
}
@@ -0,0 +1,36 @@
package com.github.manevolent.ts3j.license;
import com.github.manevolent.ts3j.enums.LicenseType;
import java.nio.ByteBuffer;
public class EphemeralLicenseUse extends LicenseUse {
public EphemeralLicenseUse() {
super();
}
@Override
public int getSize() {
return 0;
}
@Override
public LicenseUseType getUseType() {
return LicenseUseType.EPHEMERAL;
}
@Override
public LicenseType getLicenseType() {
return LicenseType.UNKNOWN;
}
@Override
public ByteBuffer write(ByteBuffer buffer) {
return buffer;
}
@Override
public ByteBuffer read(ByteBuffer buffer) {
return buffer;
}
}
@@ -0,0 +1,65 @@
package com.github.manevolent.ts3j.license;
import com.github.manevolent.ts3j.enums.LicenseType;
import java.nio.ByteBuffer;
import static com.github.manevolent.ts3j.enums.LicenseType.NO_LICENSE;
import static com.github.manevolent.ts3j.enums.LicenseType.UNKNOWN;
public class IntermediateLicenseUse extends LicenseUse {
private byte[] unknown = new byte[4];
private String issuer;
public IntermediateLicenseUse() {
super();
}
@Override
public int getSize() {
return 4 + issuer.length() + 1;
}
@Override
public LicenseUseType getUseType() {
return LicenseUseType.EPHEMERAL;
}
@Override
public LicenseType getLicenseType() {
return UNKNOWN;
}
@Override
public ByteBuffer write(ByteBuffer buffer) {
buffer.put(unknown);
License.writeNullTerminatedLicenseString(buffer, issuer);
return buffer;
}
@Override
public ByteBuffer read(ByteBuffer buffer) {
buffer.get(unknown);
issuer = License.readNullTerminatedLicenseString(buffer);
return buffer;
}
public byte[] getUnknown() {
return unknown;
}
public void setUnknown(byte[] unknown) {
this.unknown = unknown;
}
public String getIssuer() {
return issuer;
}
public void setIssuer(String issuer) {
this.issuer = issuer;
}
}
@@ -0,0 +1,182 @@
package com.github.manevolent.ts3j.license;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.List;
public class License {
public static final byte[] ROOT_KEY =
{
(byte) 0xcd, 0x0d, (byte) 0xe2, (byte) 0xae, (byte) 0xd4, 0x63, 0x45, 0x50, (byte) 0x9a,
0x7e, 0x3c, (byte) 0xfd, (byte) 0x8f, 0x68, (byte) 0xb3, (byte) 0xdc, 0x75, 0x55, (byte) 0xb2,
(byte) 0x9d, (byte) 0xcc, (byte) 0xec, 0x73, (byte) 0xcd, 0x18, 0x75, 0x0f, (byte) 0x99,
0x38, 0x12, 0x40, (byte) 0x8a
};
private byte[] computedHash;
private byte[] publicKey = new byte[32];
private byte licenseBlockType;
private long start,
end;
private LicenseUse use;
public LicenseUse getUse() {
return use;
}
public void setUse(LicenseUse use) {
this.use = use;
this.licenseBlockType = (byte) (use.getUseType().getIndex() & 0xFF);
}
public byte[] getPublicKey() {
return publicKey;
}
public void setPublicKey(byte[] publicKey) {
this.publicKey = publicKey;
}
public byte getLicenseBlockType() {
return licenseBlockType;
}
public void setLicenseBlockType(byte licenseBlockType) {
this.licenseBlockType = licenseBlockType;
}
public long getStart() {
return start;
}
public void setStart(long start) {
this.start = start;
}
public long getEnd() {
return end;
}
public void setEnd(long end) {
this.end = end;
}
public int getSize() {
return 32 + 1 + 8 + use.getSize();
}
public ByteBuffer write(ByteBuffer buffer) {
writeHeader(buffer);
writeBody(buffer);
return buffer;
}
public ByteBuffer read(ByteBuffer buffer) {
readHeader(buffer);
readBody(buffer);
return buffer;
}
public ByteBuffer readHeader(ByteBuffer buffer) {
byte keyType = buffer.get();
if (keyType != 0x00) throw new IllegalArgumentException("invalid key type: " + keyType);
buffer.get(publicKey);
licenseBlockType = buffer.get();
start = buffer.getInt() & 0x000000FFFFFF;
end = buffer.getInt() & 0x000000FFFFFF;
return buffer;
}
public ByteBuffer readBody(ByteBuffer buffer) {
LicenseUseType useType = LicenseUseType.fromId(getLicenseBlockType());
try {
use = useType.getUseClass().newInstance();
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
use.read(buffer);
return buffer;
}
public ByteBuffer writeHeader(ByteBuffer buffer) {
buffer.put((byte) 0x00);
buffer.put(publicKey);
buffer.put((byte) (licenseBlockType & 0xFF));
buffer.putInt((int) (start & 0x000000FFFFFF));
buffer.putInt((int) (end & 0x000000FFFFFF));
return buffer;
}
public ByteBuffer writeBody(ByteBuffer buffer) {
use.write(buffer);
return buffer;
}
public static String readNullTerminatedLicenseString(ByteBuffer buffer) {
StringBuilder builder = new StringBuilder();
while (buffer.remaining() > 0) {
char c = (char) buffer.get();
switch (c) {
case '\0':
break;
default:
builder.append(c);
}
}
return builder.toString();
}
public static void writeNullTerminatedLicenseString(ByteBuffer buffer, String s) {
buffer.put((s + "\0").getBytes(Charset.forName("ASCII")));
}
public static List<License> readLicenses(ByteBuffer buffer) {
List<License> licenses = new ArrayList<>();
while (buffer.remaining() > 0) {
byte licenseVersion = buffer.get();
if (licenseVersion != 0x01)
throw new IllegalArgumentException("invalid license version");
License license = new License();
license.read(buffer);
licenses.add(license);
}
return licenses;
}
public byte[] getHash() {
if (computedHash == null) {
ByteBuffer buffer = ByteBuffer.allocate(getSize());
write(buffer);
MessageDigest digest;
try {
digest = MessageDigest.getInstance("SHA512");
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
return digest.digest(buffer.array());
}
return computedHash;
}
}
@@ -0,0 +1,20 @@
package com.github.manevolent.ts3j.license;
import com.github.manevolent.ts3j.enums.LicenseType;
import java.nio.ByteBuffer;
public abstract class LicenseUse {
protected LicenseUse() {
}
public abstract int getSize();
public abstract LicenseUseType getUseType();
public abstract LicenseType getLicenseType();
public abstract ByteBuffer write(ByteBuffer buffer);
public abstract ByteBuffer read(ByteBuffer buffer);
}
@@ -0,0 +1,31 @@
package com.github.manevolent.ts3j.license;
public enum LicenseUseType {
INTERMEDIATE(0, IntermediateLicenseUse.class),
SERVER(2, ServerLicenseUse.class),
EPHEMERAL(32, EphemeralLicenseUse.class)
;
private final int index;
private final Class<? extends LicenseUse> use;
LicenseUseType(int index, Class<? extends LicenseUse> use) {
this.index = index;
this.use = use;
}
public int getIndex() {
return index;
}
public static LicenseUseType fromId(int index) {
for (LicenseUseType value : values())
if (value.getIndex() == index) return value;
throw new IllegalArgumentException("invalid index: " + index);
}
public Class<? extends LicenseUse> getUseClass() {
return use;
}
}
@@ -0,0 +1,71 @@
package com.github.manevolent.ts3j.license;
import com.github.manevolent.ts3j.enums.LicenseType;
import java.nio.ByteBuffer;
public class ServerLicenseUse extends LicenseUse {
private byte type;
private byte[] unknown = new byte[4];
private String issuer;
public ServerLicenseUse() {
super();
}
@Override
public int getSize() {
return 1 + 4 + issuer.length() + 1;
}
@Override
public LicenseUseType getUseType() {
return LicenseUseType.SERVER;
}
@Override
public ByteBuffer write(ByteBuffer buffer) {
buffer.put(type);
buffer.put(unknown);
License.writeNullTerminatedLicenseString(buffer, issuer);
return buffer;
}
@Override
public ByteBuffer read(ByteBuffer buffer) {
type = buffer.get();
buffer.get(unknown);
issuer = License.readNullTerminatedLicenseString(buffer);
return buffer;
}
public byte[] getUnknown() {
return unknown;
}
public void setUnknown(byte[] unknown) {
this.unknown = unknown;
}
public String getIssuer() {
return issuer;
}
public void setIssuer(String issuer) {
this.issuer = issuer;
}
public LicenseType getLicenseType() {
return LicenseType.fromId(type);
}
public void setType(byte type) {
this.type = type;
}
public byte getType() {
return type;
}
}
@@ -1,5 +1,6 @@
package com.github.manevolent.ts3j.protocol.packet;
import com.github.manevolent.ts3j.command.SimpleCommand;
import com.github.manevolent.ts3j.protocol.ProtocolRole;
import com.github.manevolent.ts3j.util.Ts3Logging;
@@ -17,6 +18,10 @@ public class Packet2Command extends Packet {
return text;
}
public SimpleCommand parse() {
return SimpleCommand.parse(getRole(), getText());
}
public void setText(String text) {
this.text = text;
}
@@ -1,17 +1,31 @@
package com.github.manevolent.ts3j.protocol.packet.handler.client;
import com.github.manevolent.ts3j.command.type.client.ClientInitIV;
import com.github.manevolent.ts3j.command.SimpleCommand;
import com.github.manevolent.ts3j.command.part.CommandSingleParameter;
import com.github.manevolent.ts3j.license.License;
import com.github.manevolent.ts3j.protocol.NetworkPacket;
import com.github.manevolent.ts3j.protocol.ProtocolRole;
import com.github.manevolent.ts3j.protocol.client.LocalTeamspeakClient;
import com.github.manevolent.ts3j.protocol.packet.Packet2Command;
import com.github.manevolent.ts3j.protocol.packet.Packet8Init1;
import com.github.manevolent.ts3j.util.Ts3Crypt;
import com.github.manevolent.ts3j.util.Ts3Logging;
import net.i2p.crypto.eddsa.math.GroupElement;
import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable;
import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
import org.bouncycastle.crypto.Signer;
import org.bouncycastle.crypto.digests.SHA256Digest;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.crypto.signers.DSADigestSigner;
import org.bouncycastle.crypto.signers.ECDSASigner;
import org.bouncycastle.math.ec.ECPoint;
import java.io.IOException;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.util.Base64;
import java.util.List;
import java.util.Random;
public class LocalClientHandlerConnecting extends LocalClientHandler {
@@ -118,14 +132,22 @@ public class LocalClientHandlerConnecting extends LocalClientHandler {
alphaBytes = new byte[10];
new Random().nextBytes(alphaBytes);
ClientInitIV command = new ClientInitIV();
SimpleCommand initiv = new SimpleCommand("clientinitiv", ProtocolRole.CLIENT);
command.get("alpha").set(Base64.getEncoder().encodeToString(alphaBytes));
command.get("omega").set(getClient().getLocalIdentity().getPublicKeyString());
command.get("ot").set(1); // ?
command.get("ip").set(null);
initiv.add(new CommandSingleParameter("alpha", Base64.getEncoder().encodeToString(alphaBytes)));
step4.setClientIVcommand(command.build().getBytes(Charset.forName("UTF8")));
initiv.add(new CommandSingleParameter("omega", getClient().getLocalIdentity().getPublicKeyString()));
initiv.add(new CommandSingleParameter("ot", "1")); // constant, set to 1
initiv.add(
new CommandSingleParameter(
"ip",
getClient().getOption("client.hostname", String.class)
)
);
step4.setClientIVcommand(initiv.build().getBytes(Charset.forName("UTF8")));
response4.setStep(step4);
@@ -136,7 +158,38 @@ public class LocalClientHandlerConnecting extends LocalClientHandler {
}
} else if (packet.getPacket() instanceof Packet2Command) {
Ts3Logging.debug(((Packet2Command) packet.getPacket()).getText());
SimpleCommand command = ((Packet2Command) packet.getPacket()).parse();
if (command.getName().equalsIgnoreCase("initivexpand2")) {
// 3.2.2 initivexpand2 (Client <- Server)
Ts3Logging.debug("initivexpand2");
if (!command.get("ot").getValue().equals("1"))
throw new IllegalArgumentException("ot constant != 1: " + command.get("ot").getValue());
byte[] license = Base64.getDecoder().decode(command.get("l").getValue());
byte[] licsense_validation = command.get("tvd").getValue() == null ?
null :
Base64.getDecoder().decode(command.get("tvd").getValue());
byte[] random = Base64.getDecoder().decode(command.get("beta").getValue()); // beta
byte[] publicKeyBytes = Base64.getDecoder().decode(command.get("omega").getValue()); // omega
byte[] proof = Base64.getDecoder().decode(command.get("proof").getValue()); // ecdh_sign(1)
// 3.2.1.1 Verify integrity
// The proof parameter is the sign of the l parameter (not base64 encoded). The client can verify the l
// parameter with the public key of the server which is sent in omega.
ECPoint publicKey = Ts3Crypt.decodePublicKey(publicKeyBytes);
if (!Ts3Crypt.verifySignature(publicKey, license, proof))
throw new SecurityException("invalid proof signature: " + Ts3Logging.getHex(proof));
// 3.2.2.2 Parsing the license
List<License> licenses = License.readLicenses(ByteBuffer.wrap(license));
}
}
}
}
@@ -60,10 +60,11 @@ public class LocalTeamspeakSocket extends AbstractTeamspeakSocket implements Tea
packet.setSocketAddress(datagramSocket.getRemoteSocketAddress());
Ts3Logging.debug(
"[NETWORK] WRITE Len=" + packet.getLength() + " to " + packet.getSocketAddress() +
"\n" + Ts3Logging.getHex(packet.getData(), packet.getLength())
"[NETWORK] WRITE Len=" + packet.getLength() + " to " + packet.getSocketAddress()
);
//Ts3Logging.debug(Ts3Logging.getHex(packet.getData(), packet.getLength()));
datagramSocket.send(packet);
}
@@ -116,10 +117,11 @@ public class LocalTeamspeakSocket extends AbstractTeamspeakSocket implements Tea
datagramSocket.receive(datagramPacket);
Ts3Logging.debug(
"[NETWORK] READ Len=" + datagramPacket.getLength() + " from " + datagramPacket.getSocketAddress() +
"\n" + Ts3Logging.getHex(datagramPacket.getData(), datagramPacket.getLength())
"[NETWORK] READ Len=" + datagramPacket.getLength() + " from " + datagramPacket.getSocketAddress()
);
//Ts3Logging.debug(Ts3Logging.getHex(datagramPacket.getData(), datagramPacket.getLength()));
return datagramPacket;
}
@@ -0,0 +1,82 @@
package com.github.manevolent.ts3j.util;
import org.bouncycastle.asn1.*;
import org.bouncycastle.crypto.digests.SHA256Digest;
import org.bouncycastle.crypto.params.ECDomainParameters;
import org.bouncycastle.crypto.params.ECKeyGenerationParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.crypto.signers.DSADigestSigner;
import org.bouncycastle.crypto.signers.ECDSASigner;
import org.bouncycastle.jce.ECNamedCurveTable;
import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec;
import org.bouncycastle.math.ec.ECPoint;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
public final class Ts3Crypt {
private static ECDomainParameters getDomainParameters() {
ECNamedCurveParameterSpec ecp = ECNamedCurveTable.getParameterSpec("prime256v1");
return new ECDomainParameters(ecp.getCurve(), ecp.getG(), ecp.getN(), ecp.getH(), ecp.getSeed());
}
public static boolean verifySignature(ECPoint publicKey, byte[] data, byte[] signature) {
DSADigestSigner signer = new DSADigestSigner(new ECDSASigner(), new SHA256Digest());
ECPublicKeyParameters signingKey = new ECPublicKeyParameters(publicKey, getDomainParameters());
signer.init(false, signingKey);
signer.update(data, 0, data.length);
return signer.verifySignature(signature);
}
/**
* From http://stackoverflow.com/questions/2409618/how-do-i-decode-a-der-encoded-string-in-java
*/
private static ASN1Encodable[] readDERObject(byte[] data) throws IOException
{
ByteArrayInputStream inStream = new ByteArrayInputStream(data);
ASN1InputStream asnInputStream = new ASN1InputStream(inStream);
ASN1Object object = asnInputStream.readObject();
if (object instanceof DLSequence) {
return ((DLSequence) object).toArray();
} else {
return new ASN1Encodable[] { object };
}
}
public static ECPoint decodePublicKey(byte[] bytes) {
try {
DERSequence asnKeyData = new DERSequence(readDERObject(bytes));
BigInteger x = ((org.bouncycastle.asn1.ASN1Integer)asnKeyData.getObjectAt(2)).getValue();
BigInteger y = ((org.bouncycastle.asn1.ASN1Integer)asnKeyData.getObjectAt(3)).getValue();
return getDomainParameters().getCurve().createPoint(x, y);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static byte[] encodePublicKey(ECPoint publicKey) {
try {
byte[] dataArray = new DERSequence(new ASN1Encodable[]{
new DERBitString(new byte[]{0b0000_0000}, 7),
new DERInteger(32),
new DERInteger(publicKey.getAffineXCoord().toBigInteger()),
new DERInteger(publicKey.getAffineYCoord().toBigInteger())
}).getEncoded();
return dataArray;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
@@ -43,7 +43,7 @@ public final class Ts3String {
default:
throw new IllegalArgumentException("invalid escape code: " + c);
}
}
} else sb.append(c);
}
return sb.toString();
@@ -21,7 +21,7 @@ public class ServerConnectionTest extends TestCase {
client.setLocalIdentity(localIdentity);
client.connect(new InetSocketAddress(
"voice.teamspeak.com",
"ts.teamlixo.net",
9987),
null,
10000L