Ts3 INI identity loading

This commit is contained in:
Manevolent
2018-11-05 14:48:31 -05:00
parent 3987f3c097
commit 91187964c5
4 changed files with 187 additions and 1 deletions
@@ -1,6 +1,9 @@
package com.github.manevolent.ts3j.identity;
import com.github.manevolent.ts3j.util.Ts3Crypt;
import com.github.manevolent.ts3j.util.Ts3Debugging;
import org.bouncycastle.asn1.ASN1Object;
import org.bouncycastle.asn1.DERSequence;
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
import org.bouncycastle.crypto.generators.ECKeyPairGenerator;
import org.bouncycastle.crypto.params.ECDomainParameters;
@@ -10,11 +13,32 @@ import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.jce.ECNamedCurveTable;
import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec;
import org.bouncycastle.math.ec.ECPoint;
import org.ini4j.Ini;
import java.io.*;
import java.math.BigInteger;
import java.nio.charset.Charset;
import java.security.*;
import java.util.Base64;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class LocalIdentity extends Identity {
/**
* Pattern used for Identity.identity value in TS3 identity INI files.
*/
private static final Pattern identityPattern = Pattern.compile("^\"(\\d+)V([\\w\\/\\+]+={0,2})\"$", Pattern.MULTILINE);
/**
* Key used to obfuscate the key component(s) in official TS3 identity INI files.
*/
private static final byte[] identityFileObfuscationKey =
("b9dfaa7bee6ac57ac7b65f1094a1c155e747327bc2fe5d5" +
"1c512023fe54a280201004e90ad1daaae1075d53" +
"b7d571c30e063b5a62a4a017bb394833aa0983e6e").getBytes(Charset.forName("ASCII"));
/**
* Identity private component.
*/
private final BigInteger privateKey;
public LocalIdentity(ECPoint publicKey, BigInteger privateKey) {
@@ -23,15 +47,32 @@ public class LocalIdentity extends Identity {
this.privateKey = privateKey;
}
/**
* Loads an identity from a specific key-pair.
* @param publicKey public key component
* @param privateKey private key component
* @return local identity with given components
*/
public static LocalIdentity load(ECPoint publicKey,
BigInteger privateKey) {
return new LocalIdentity(publicKey, privateKey);
}
/**
* Loads an identity, re-generating the public component from the private component.
* @param privateKey private key component
* @return local identity with given components
*/
public static LocalIdentity load(BigInteger privateKey) {
return new LocalIdentity(Ts3Crypt.generatePublicKeyFromPrivateKey(privateKey), privateKey);
}
/**
* Generates a new identity with a given security level target.
* @param securityLevel security level to generate for (may take time)
* @return local identity with given security level
* @throws GeneralSecurityException
*/
public static LocalIdentity generateNew(int securityLevel) throws GeneralSecurityException {
ECNamedCurveParameterSpec ecp = ECNamedCurveTable.getParameterSpec("prime256v1");
ECDomainParameters domainParams =
@@ -55,7 +96,85 @@ public class LocalIdentity extends Identity {
return Ts3Crypt.createSignature(privateKey, data);
}
/**
* Gets the identity's private component.
* @return private key.
*/
public BigInteger getPrivateKey() {
return privateKey;
}
/**
* Reads a TS3 identity from a given TS3 identity INI file.
* @param iniFile INI file to read
* @return local identity
*/
public static final LocalIdentity read(File iniFile) throws IOException {
return read(new FileInputStream(iniFile));
}
/**
* Reads a TS3 identity from a given input stream.
* @param inputStream Input stream to read
* @return local identity
*/
public static final LocalIdentity read(InputStream inputStream) throws IOException {
Ini ini = new Ini(inputStream);
String identityValue = ini.get("Identity", "identity");
if (identityValue == null)
throw new IOException(new IllegalArgumentException("missing identity value"));
Matcher matcher = identityPattern.matcher(identityValue);
if (!matcher.matches())
throw new IOException(new IllegalArgumentException("invalid INI file"));
long level = Long.parseUnsignedLong(matcher.group(1));
//var ident = Base64Decode(match.Groups["identity"].Value);
byte[] identityData = Base64.getDecoder().decode(matcher.group(2));
if (identityData.length < 20) throw new IOException(new IllegalArgumentException("identity data too short"));
//int nullIdx = identityArr.AsSpan(20).IndexOf((byte)0);
int nullIndex = -1;
for (int i = 20; i < identityData.length; i ++)
if (identityData[i] == 0x0) {
nullIndex = i-20;
break;
}
//var hash = Hash1It(identityArr, 20, nullIdx < 0 ? identityArr.Length - 20 : nullIdx);
byte[] hash = Ts3Crypt.hash128(
identityData,
20,
nullIndex < 0 ? identityData.length - 20 : nullIndex
);
//XorBinary(ReadOnlySpan<byte> a, ReadOnlySpan<byte> b, int len, Span<byte> outBuf)
//XorBinary(identityArr, hash, 20, identityArr);
Ts3Crypt.xor(
identityData, 0,
hash, 0,
20,
identityData, 0
);
//XorBinary(identityArr, Ts3IdentityObfuscationKey, Math.Min(100, identityArr.Length), identityArr);
Ts3Crypt.xor(
identityData, 0,
identityFileObfuscationKey, 0,
Math.min(100, identityData.length),
identityData, 0
);
String utf8Decoded = new String(identityData, Charset.forName("UTF8"));
LocalIdentity identity = Ts3Crypt.loadIdentityFromAsn(Base64.getDecoder().decode(utf8Decoded));
identity.setKeyOffset(level);
identity.setLastCheckedKeyOffset(level);
return identity;
}
}
@@ -8,6 +8,7 @@ import Punisher.NaCl.Internal.Sha512;
import com.github.manevolent.ts3j.identity.*;
import com.github.manevolent.ts3j.license.License;
import org.bouncycastle.asn1.*;
import org.bouncycastle.crypto.KeyGenerationParameters;
import org.bouncycastle.crypto.digests.SHA256Digest;
import org.bouncycastle.crypto.params.ECDomainParameters;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
@@ -124,7 +125,7 @@ public final class Ts3Crypt {
return parameters;
}
private static void xor(byte[] a, int aoffs, byte[] b, int boffs, int len, byte[] outBuf, int outOffs)
public 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();
@@ -290,6 +291,28 @@ public final class Ts3Crypt {
return ECNamedCurveTable.getParameterSpec("prime256v1").getG().multiply(privateKey).normalize();
}
public static LocalIdentity loadIdentityFromAsn(byte[] asnData) throws IOException {
ECPoint publicKey;
BigInteger privateKey = null;
DERSequence asnKeyData = new DERSequence(readDERObject(asnData));
int bitInfo = ((DERBitString)asnKeyData.getObjectAt(0)).intValue();
if (bitInfo == 0 || bitInfo == 128) {
BigInteger x = ((ASN1Integer)asnKeyData.getObjectAt(2)).getValue();
BigInteger y = ((ASN1Integer)asnKeyData.getObjectAt(3)).getValue();
publicKey = getDomainParameters().getCurve().createPoint(x, y);
if (bitInfo == 128)
privateKey = ((ASN1Integer)asnKeyData.getObjectAt(4)).getValue();
} else if (bitInfo == 192) {
privateKey = ((ASN1Integer)asnKeyData.getObjectAt(2)).getValue();
publicKey = generatePublicKeyFromPrivateKey(privateKey);
} else throw new IllegalArgumentException("invalid DER sequence");
return new LocalIdentity(publicKey, privateKey);
}
public static final class SecureChannelParameters {
private final byte[] fakeSignature;
private final byte[] ivStruct;
@@ -0,0 +1,38 @@
package com.github.manevolent.ts3j;
import com.github.manevolent.ts3j.identity.LocalIdentity;
import com.github.manevolent.ts3j.util.Ts3Crypt;
import com.github.manevolent.ts3j.util.Ts3Debugging;
import junit.framework.TestCase;
import java.io.ByteArrayInputStream;
import java.io.InputStreamReader;
import java.io.StringReader;
import java.nio.charset.Charset;
public class IdentityFileTest extends TestCase {
public static void main(String[] args) throws Exception {
new IdentityFileTest().testParser();
}
public void testParser() throws Exception {
Ts3Debugging.setEnabled(true);
String identityFileContents = "[Identity]\n" +
"id=New identity_1\n" +
"identity=\"68616VFe2bVjeiXc+7ULXTnBkEdLZOpjIGP15CFmYbBix+T1EqQEd+WXAPNExRKA8MLl" +
"F/MmxzWwJ3BDwrT1sie31RRwdVSUd5XVVJCTZ+EQIKGAkeZF8HUU9/VwUoWl1mKAJRHGB+QHpXMEFBa" +
"UVBa0lBTkU2VFpDcHE1K2cvamVjbTR0K1FhNmI3VVdHR2o3Nys4ZTFQYm14Zz0=\"\n" +
"nickname=TS3JTest\n" +
"phonetic_nickname=\n";
String uuid = "jJbyG5OzsAkEVGsRawC4VhLfeW4=";
int securityLevel = 15;
LocalIdentity localIdentity =
LocalIdentity.read(new ByteArrayInputStream(identityFileContents.getBytes(Charset.forName("UTF8"))));
assertEquals(uuid, localIdentity.getUid().toBase64());
assertEquals(securityLevel, localIdentity.getSecurityLevel());
}
}