Mostly done in theory now. Not responding to last packet however.

This commit is contained in:
Manevolent
2018-07-25 11:38:22 -04:00
parent 4c7d011b01
commit 2dd96d39f3
26 changed files with 6361 additions and 11 deletions
@@ -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;
@@ -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;
}
}
}