Mostly done in theory now. Not responding to last packet however.
This commit is contained in:
@@ -80,7 +80,6 @@ public class SimpleCommand implements Command {
|
||||
} else {
|
||||
String[] commandParamParts = commandParamText.split("=", 2);
|
||||
|
||||
Ts3Logging.debug(commandParamText);
|
||||
parameterList.add(new CommandSingleParameter(
|
||||
commandParamParts[0].toLowerCase(),
|
||||
commandParamParts.length > 1 ?
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.github.manevolent.ts3j.identity;
|
||||
|
||||
import com.github.manevolent.ts3j.util.Ts3Crypt;
|
||||
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
|
||||
import org.bouncycastle.crypto.generators.ECKeyPairGenerator;
|
||||
import org.bouncycastle.crypto.params.ECDomainParameters;
|
||||
@@ -56,4 +57,8 @@ public class LocalIdentity extends Identity {
|
||||
|
||||
return localIdentity;
|
||||
}
|
||||
|
||||
public byte[] sign(byte[] data) {
|
||||
return Ts3Crypt.createSignature(privateKey, data);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
package com.github.manevolent.ts3j.license;
|
||||
|
||||
import Punisher.NaCl.Internal.Ed25519Ref10.*;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.Charset;
|
||||
import java.security.DigestException;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.ArrayList;
|
||||
@@ -67,7 +70,7 @@ public class License {
|
||||
}
|
||||
|
||||
public int getSize() {
|
||||
return 32 + 1 + 8 + use.getSize();
|
||||
return 1 + 32 + 1 + 8 + use.getSize();
|
||||
}
|
||||
|
||||
public ByteBuffer write(ByteBuffer buffer) {
|
||||
@@ -168,15 +171,60 @@ public class License {
|
||||
write(buffer);
|
||||
|
||||
MessageDigest digest;
|
||||
|
||||
try {
|
||||
digest = MessageDigest.getInstance("SHA512");
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
return digest.digest(buffer.array());
|
||||
digest.update(buffer.array(), 1, buffer.array().length - 1);
|
||||
return digest.digest();
|
||||
}
|
||||
|
||||
return computedHash;
|
||||
}
|
||||
|
||||
public byte[] deriveKey(byte[] parent) {
|
||||
// ScalarOperations.sc_clamp(Hash);
|
||||
ScalarOperations.sc_clamp(getHash(), 0);
|
||||
|
||||
// GroupOperations.ge_frombytes_negate_vartime(out var pubkey, Key);
|
||||
GroupElementP3 pubkey = new GroupElementP3();
|
||||
GroupOperations.ge_frombytes_negate_vartime(pubkey, getPublicKey(), 0);
|
||||
|
||||
// GroupOperations.ge_frombytes_negate_vartime(out var parkey, parent);
|
||||
GroupElementP3 parkey = new GroupElementP3();
|
||||
GroupOperations.ge_frombytes_negate_vartime(parkey, parent, 0);
|
||||
|
||||
GroupElementP1P1 res = new GroupElementP1P1();
|
||||
GroupOperations.ge_scalarmult_vartime(res, getHash(), pubkey);
|
||||
|
||||
GroupElementCached pargrp = new GroupElementCached();
|
||||
GroupOperations.ge_p3_to_cached(pargrp, parkey);
|
||||
|
||||
GroupElementP3 r = new GroupElementP3();
|
||||
GroupOperations.ge_p1p1_to_p3(r, res);
|
||||
|
||||
GroupElementP1P1 a = new GroupElementP1P1() ;
|
||||
GroupOperations.ge_add(a, r, pargrp);
|
||||
|
||||
GroupElementP3 r2 = new GroupElementP3();
|
||||
GroupOperations.ge_p1p1_to_p3(r2, a);
|
||||
|
||||
byte[] final_ = new byte[32];
|
||||
GroupOperations.ge_p3_tobytes(final_, 0, r2);
|
||||
|
||||
final_[1] ^= 0x80;
|
||||
|
||||
return final_;
|
||||
}
|
||||
|
||||
public static byte[] deriveKey(List<License> blocks) {
|
||||
byte[] round = ROOT_KEY;
|
||||
|
||||
for (License block : blocks) round = block.deriveKey(round);
|
||||
|
||||
return round;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.github.manevolent.ts3j.license;
|
||||
|
||||
import com.github.manevolent.ts3j.enums.LicenseType;
|
||||
import com.github.manevolent.ts3j.util.Ts3Logging;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
@@ -27,6 +28,7 @@ public class ServerLicenseUse extends LicenseUse {
|
||||
public ByteBuffer write(ByteBuffer buffer) {
|
||||
buffer.put(type);
|
||||
buffer.put(unknown);
|
||||
|
||||
License.writeNullTerminatedLicenseString(buffer, issuer);
|
||||
|
||||
return buffer;
|
||||
@@ -36,6 +38,7 @@ public class ServerLicenseUse extends LicenseUse {
|
||||
public ByteBuffer read(ByteBuffer buffer) {
|
||||
type = buffer.get();
|
||||
buffer.get(unknown);
|
||||
|
||||
issuer = License.readNullTerminatedLicenseString(buffer);
|
||||
|
||||
return buffer;
|
||||
|
||||
+30
-5
@@ -10,6 +10,7 @@ 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 javafx.util.Pair;
|
||||
import org.bouncycastle.math.ec.ECPoint;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -165,21 +166,45 @@ public class LocalClientHandlerConnecting extends LocalClientHandler {
|
||||
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[] beta = Base64.getDecoder().decode(command.get("beta").getValue()); // beta
|
||||
byte[] omega = Base64.getDecoder().decode(command.get("omega").getValue()); // omega
|
||||
byte[] proof = Base64.getDecoder().decode(command.get("proof").getValue()); // ecdh_sign(1)
|
||||
|
||||
Pair<byte[], byte[]> keyPair = Ts3Crypt.generateKeypair();
|
||||
|
||||
// 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);
|
||||
ECPoint publicKey = Ts3Crypt.decodePublicKey(omega);
|
||||
|
||||
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));
|
||||
// generate proof for clientek
|
||||
byte[] toSign = new byte[86];
|
||||
System.arraycopy(keyPair.getKey(), 0, toSign, 0, 32);
|
||||
System.arraycopy(beta, 0, toSign, 32, 54);
|
||||
byte[] sign = getClient().getLocalIdentity().sign(toSign);
|
||||
|
||||
Ts3Logging.debug("Created proof (clientek): " + Ts3Logging.getHex(sign));
|
||||
|
||||
// Send clientek (telling them the shared secret)
|
||||
Packet2Command clientek = new Packet2Command(ProtocolRole.CLIENT);
|
||||
clientek.setText(
|
||||
new SimpleCommand(
|
||||
"clientek", ProtocolRole.CLIENT,
|
||||
new CommandSingleParameter("ek", Base64.getEncoder().encodeToString(keyPair.getKey())),
|
||||
new CommandSingleParameter("proof", Base64.getEncoder().encodeToString(sign))
|
||||
).build()
|
||||
);
|
||||
getClient().send(clientek);
|
||||
|
||||
Ts3Crypt.cryptoInit2(license, alphaBytes, omega, proof, beta, keyPair.getValue());
|
||||
|
||||
// send client init...
|
||||
|
||||
// DONE!
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,16 @@
|
||||
package com.github.manevolent.ts3j.util;
|
||||
|
||||
import Punisher.NaCl.Internal.Ed25519Ref10.GroupElementP2;
|
||||
import Punisher.NaCl.Internal.Ed25519Ref10.GroupElementP3;
|
||||
import Punisher.NaCl.Internal.Ed25519Ref10.GroupOperations;
|
||||
import Punisher.NaCl.Internal.Ed25519Ref10.ScalarOperations;
|
||||
import Punisher.NaCl.Internal.Sha512;
|
||||
import com.github.manevolent.ts3j.license.License;
|
||||
import javafx.util.Pair;
|
||||
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.ECPrivateKeyParameters;
|
||||
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
|
||||
import org.bouncycastle.crypto.signers.DSADigestSigner;
|
||||
import org.bouncycastle.crypto.signers.ECDSASigner;
|
||||
@@ -14,12 +21,92 @@ import org.bouncycastle.math.ec.ECPoint;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.math.BigInteger;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
public final class Ts3Crypt {
|
||||
public static Ts3Crypt.SecureChannelParameters cryptoInit2(byte[] license, byte[] alpha,
|
||||
byte[] omega, byte[] proof,
|
||||
byte[] beta, byte[] privateKey) {
|
||||
// 3.2.2.2 Parsing the license
|
||||
List<License> licenses = License.readLicenses(ByteBuffer.wrap(license));
|
||||
|
||||
byte[] key = License.deriveKey(licenses);
|
||||
|
||||
Ts3Logging.debug("Computed license key: " + Ts3Logging.getHex(key));
|
||||
|
||||
byte[] sharedSecret = Ts3Crypt.generateSharedSecret2(key, privateKey);
|
||||
|
||||
Ts3Logging.debug("Computed shared secret: " + Ts3Logging.getHex(sharedSecret));
|
||||
|
||||
Ts3Crypt.SecureChannelParameters parameters =
|
||||
Ts3Crypt.getSecureParameters(alpha, beta, sharedSecret);
|
||||
|
||||
Ts3Logging.debug("Computed fake fingerprint: " + Ts3Logging.getHex(parameters.getFakeSignature()));
|
||||
Ts3Logging.debug("Computed IV struct: " + Ts3Logging.getHex(parameters.getIvStruct()));
|
||||
|
||||
return parameters;
|
||||
}
|
||||
|
||||
private static void xor(byte[] a, int aoffs, byte[] b, int boffs, int len, byte[] outBuf, int outOffs)
|
||||
{
|
||||
if (a.length < len || b.length < len || outBuf.length < len)
|
||||
throw new ArrayIndexOutOfBoundsException();
|
||||
|
||||
for (int i = 0; i < len; i++)
|
||||
outBuf[i + outOffs] = (byte)(a[i + aoffs] ^ b[i + boffs]);
|
||||
}
|
||||
|
||||
public static SecureChannelParameters getSecureParameters(byte[] alpha, byte[] beta, byte[] sharedKey) {
|
||||
if (beta.length != 10 && beta.length != 54)
|
||||
throw new IllegalArgumentException("invalid beta size (" + beta.length + ")");
|
||||
|
||||
byte[] fakeSignature = new byte[8];
|
||||
byte[] ivStruct = new byte[10 + beta.length];
|
||||
|
||||
xor(sharedKey, 0, alpha, 0, alpha.length, ivStruct, 0);
|
||||
xor(sharedKey, 10, beta, 0, beta.length, ivStruct, 10);
|
||||
|
||||
byte[] buffer;
|
||||
|
||||
try {
|
||||
buffer = MessageDigest.getInstance("SHA1").digest(ivStruct);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
System.arraycopy(buffer, 0, fakeSignature, 0, 8);
|
||||
|
||||
return new SecureChannelParameters(fakeSignature, ivStruct);
|
||||
}
|
||||
|
||||
public static byte[] generateSharedSecret2(byte[] publicKey, byte[] privateKey) {
|
||||
byte[] privateKeyCopy = new byte[32];
|
||||
System.arraycopy(privateKey, 0, privateKeyCopy, 0, 32);
|
||||
|
||||
privateKeyCopy[31] &= 0x7F;
|
||||
|
||||
GroupElementP3 pub1 = new GroupElementP3();
|
||||
GroupOperations.ge_frombytes_negate_vartime(pub1, publicKey, 0);
|
||||
|
||||
GroupElementP2 mul = new GroupElementP2();
|
||||
GroupOperations.ge_scalarmult_vartime(mul, privateKeyCopy, pub1);
|
||||
|
||||
byte[] sharedTmp = new byte[32];
|
||||
GroupOperations.ge_tobytes(sharedTmp, 0, mul);
|
||||
|
||||
sharedTmp[31] ^= 0x80;
|
||||
|
||||
try {
|
||||
return Sha512.Hash(sharedTmp);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static ECDomainParameters getDomainParameters() {
|
||||
ECNamedCurveParameterSpec ecp = ECNamedCurveTable.getParameterSpec("prime256v1");
|
||||
return new ECDomainParameters(ecp.getCurve(), ecp.getG(), ecp.getN(), ecp.getH(), ecp.getSeed());
|
||||
@@ -35,6 +122,16 @@ public final class Ts3Crypt {
|
||||
return signer.verifySignature(signature);
|
||||
}
|
||||
|
||||
public static byte[] createSignature(BigInteger privateKey, byte[] data) {
|
||||
DSADigestSigner signer = new DSADigestSigner(new ECDSASigner(), new SHA256Digest());
|
||||
ECPrivateKeyParameters signingKey = new ECPrivateKeyParameters(privateKey, getDomainParameters());
|
||||
|
||||
signer.init(true, signingKey);
|
||||
signer.update(data, 0, data.length);
|
||||
|
||||
return signer.generateSignature();
|
||||
}
|
||||
|
||||
/**
|
||||
* From http://stackoverflow.com/questions/2409618/how-do-i-decode-a-der-encoded-string-in-java
|
||||
*/
|
||||
@@ -79,4 +176,37 @@ public final class Ts3Crypt {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static Pair<byte[], byte[]> generateKeypair() {
|
||||
byte[] privateKey = new byte[32];
|
||||
new SecureRandom().nextBytes(privateKey);
|
||||
|
||||
ScalarOperations.sc_clamp(privateKey, 0);
|
||||
|
||||
GroupElementP3 A = new GroupElementP3();
|
||||
GroupOperations.ge_scalarmult_base(A, privateKey, 0);
|
||||
|
||||
byte[] publicKey = new byte[32];
|
||||
GroupOperations.ge_p3_tobytes(publicKey, 0, A);
|
||||
|
||||
return new Pair<>(publicKey, privateKey);
|
||||
}
|
||||
|
||||
public static final class SecureChannelParameters {
|
||||
private final byte[] fakeSignature;
|
||||
private final byte[] ivStruct;
|
||||
|
||||
public SecureChannelParameters(byte[] fakeSignature, byte[] ivStruct) {
|
||||
this.fakeSignature = fakeSignature;
|
||||
this.ivStruct = ivStruct;
|
||||
}
|
||||
|
||||
public byte[] getFakeSignature() {
|
||||
return fakeSignature;
|
||||
}
|
||||
|
||||
public byte[] getIvStruct() {
|
||||
return ivStruct;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user