Работа над разбором TLS пакетов

This commit is contained in:
serega6531
2020-04-18 22:05:59 +03:00
parent 1e9d327af0
commit 10dd6005a2
10 changed files with 378 additions and 7 deletions

View File

@@ -7,6 +7,7 @@ import org.pcap4j.util.ByteArrays;
import ru.serega6531.packmate.service.optimization.tls.numbers.ContentType; import ru.serega6531.packmate.service.optimization.tls.numbers.ContentType;
import ru.serega6531.packmate.service.optimization.tls.numbers.TlsVersion; import ru.serega6531.packmate.service.optimization.tls.numbers.TlsVersion;
import ru.serega6531.packmate.service.optimization.tls.records.ChangeCipherSpecRecord; import ru.serega6531.packmate.service.optimization.tls.records.ChangeCipherSpecRecord;
import ru.serega6531.packmate.service.optimization.tls.records.HandshakeRecord;
import ru.serega6531.packmate.service.optimization.tls.records.TlsRecord; import ru.serega6531.packmate.service.optimization.tls.records.TlsRecord;
import java.util.ArrayList; import java.util.ArrayList;
@@ -72,12 +73,13 @@ public class TlsPacket extends AbstractPacket {
} }
private TlsHeader(byte[] rawData, int offset, int length) throws IllegalRawDataException { private TlsHeader(byte[] rawData, int offset, int length) throws IllegalRawDataException {
//TODO check length
this.contentType = ContentType.getInstance(ByteArrays.getByte(rawData, CONTENT_TYPE_OFFSET + offset)); this.contentType = ContentType.getInstance(ByteArrays.getByte(rawData, CONTENT_TYPE_OFFSET + offset));
this.version = TlsVersion.getInstance(ByteArrays.getShort(rawData, VERSION_OFFSET + offset)); this.version = TlsVersion.getInstance(ByteArrays.getShort(rawData, VERSION_OFFSET + offset));
this.length = ByteArrays.getShort(rawData, LENGTH_OFFSET + offset); this.length = ByteArrays.getShort(rawData, LENGTH_OFFSET + offset);
if (contentType == ContentType.HANDSHAKE) { if (contentType == ContentType.HANDSHAKE) {
this.record = HandshakeRecord.newInstance(rawData, offset + RECORD_OFFSET, length);
} else if (contentType == ContentType.CHANGE_CIPHER_SPEC) { } else if (contentType == ContentType.CHANGE_CIPHER_SPEC) {
this.record = ChangeCipherSpecRecord.newInstance(rawData, offset + RECORD_OFFSET, length); this.record = ChangeCipherSpecRecord.newInstance(rawData, offset + RECORD_OFFSET, length);
} else if (contentType == ContentType.APPLICATION_DATA) { } else if (contentType == ContentType.APPLICATION_DATA) {
@@ -102,6 +104,14 @@ public class TlsPacket extends AbstractPacket {
public int length() { public int length() {
return RECORD_OFFSET + length; return RECORD_OFFSET + length;
} }
@Override
protected String buildString() {
return "TLS Header [" + length() + " bytes]\n" +
" Version: " + version + "\n" +
" Type: " + contentType + "\n" +
record.toString();
}
} }
public static final class Builder extends AbstractBuilder { public static final class Builder extends AbstractBuilder {

View File

@@ -0,0 +1,37 @@
package ru.serega6531.packmate.service.optimization.tls.numbers;
import org.pcap4j.packet.namednumber.NamedNumber;
import java.util.HashMap;
import java.util.Map;
public class CipherSuite extends NamedNumber<Short, CipherSuite> {
public static final CipherSuite RESERVED_GREASE = new CipherSuite((short) 0xdada, "Reserved (GREASE)");
public static final CipherSuite TLS_AES_128_GCM_SHA256 = new CipherSuite((short) 0x1301, "TLS_AES_128_GCM_SHA256");
private static final Map<Short, CipherSuite> registry = new HashMap<>();
static {
registry.put(RESERVED_GREASE.value(), RESERVED_GREASE);
registry.put(TLS_AES_128_GCM_SHA256.value(), TLS_AES_128_GCM_SHA256);
//TODO add all
}
public CipherSuite(Short value, String name) {
super(value, name);
}
public static CipherSuite getInstance(Short value) {
if (registry.containsKey(value)) {
return registry.get(value);
} else {
return new CipherSuite(value, "unknown");
}
}
@Override
public int compareTo(CipherSuite o) {
return 0;
}
}

View File

@@ -0,0 +1,34 @@
package ru.serega6531.packmate.service.optimization.tls.numbers;
import org.pcap4j.packet.namednumber.NamedNumber;
import java.util.HashMap;
import java.util.Map;
public class CompressionMethod extends NamedNumber<Byte, CompressionMethod> {
public static final CompressionMethod NULL = new CompressionMethod((byte) 0, "null");
private static final Map<Byte, CompressionMethod> registry = new HashMap<>();
static {
registry.put(NULL.value(), NULL);
}
public CompressionMethod(Byte value, String name) {
super(value, name);
}
public static CompressionMethod getInstance(Byte value) {
if (registry.containsKey(value)) {
return registry.get(value);
} else {
return new CompressionMethod(value, "Unknown");
}
}
@Override
public int compareTo(CompressionMethod o) {
return value().compareTo(o.value());
}
}

View File

@@ -0,0 +1,35 @@
package ru.serega6531.packmate.service.optimization.tls.numbers;
import org.pcap4j.packet.namednumber.NamedNumber;
import java.util.HashMap;
import java.util.Map;
public class ExtensionType extends NamedNumber<Short, ExtensionType> {
public static final ExtensionType RESERVED_GREASE = new ExtensionType((short) 14906, "Reserved (GREASE)");
private static final Map<Short, ExtensionType> registry = new HashMap<>();
static {
registry.put(RESERVED_GREASE.value(), RESERVED_GREASE);
//TODO add all
}
public ExtensionType(Short value, String name) {
super(value, name);
}
public static ExtensionType getInstance(Short value) {
if (registry.containsKey(value)) {
return registry.get(value);
} else {
return new ExtensionType(value, "Unknown");
}
}
@Override
public int compareTo(ExtensionType o) {
return value().compareTo(o.value());
}
}

View File

@@ -0,0 +1,52 @@
package ru.serega6531.packmate.service.optimization.tls.numbers;
import org.pcap4j.packet.namednumber.NamedNumber;
import java.util.HashMap;
import java.util.Map;
public class HandshakeType extends NamedNumber<Byte, HandshakeType> {
public static final HandshakeType HELLO_REQUEST = new HandshakeType((byte) 0, "Hello Request");
public static final HandshakeType CLIENT_HELLO = new HandshakeType((byte) 1, "Client Hello");
public static final HandshakeType SERVER_HELLO = new HandshakeType((byte) 2, "Server Hello");
public static final HandshakeType CERTIFICATE = new HandshakeType((byte) 11, "Certificate");
public static final HandshakeType SERVER_KEY_EXCHANGE = new HandshakeType((byte) 12, "Server Key Excange");
public static final HandshakeType CERTIFICATE_REQUEST = new HandshakeType((byte) 13, "Certificate Request");
public static final HandshakeType SERVER_HELLO_DONE = new HandshakeType((byte) 14, "Server Hello Done");
public static final HandshakeType CERTIFICATE_VERIFY = new HandshakeType((byte) 15, "Certificate Verify");
public static final HandshakeType CLIENT_KEY_EXCHANGE = new HandshakeType((byte) 16, "Client Key Exchange");
public static final HandshakeType FINISHED = new HandshakeType((byte) 20, "Finished");
private static final Map<Byte, HandshakeType> registry = new HashMap<>();
static {
registry.put(HELLO_REQUEST.value(), HELLO_REQUEST);
registry.put(CLIENT_HELLO.value(), CLIENT_HELLO);
registry.put(SERVER_HELLO.value(), SERVER_HELLO);
registry.put(CERTIFICATE.value(), CERTIFICATE);
registry.put(SERVER_KEY_EXCHANGE.value(), SERVER_KEY_EXCHANGE);
registry.put(CERTIFICATE_REQUEST.value(), CERTIFICATE_REQUEST);
registry.put(SERVER_HELLO_DONE.value(), SERVER_HELLO_DONE);
registry.put(CERTIFICATE_VERIFY.value(), CERTIFICATE_VERIFY);
registry.put(CLIENT_KEY_EXCHANGE.value(), CLIENT_KEY_EXCHANGE);
registry.put(FINISHED.value(), FINISHED);
}
public HandshakeType(Byte value, String name) {
super(value, name);
}
public static HandshakeType getInstance(Byte value) {
if (registry.containsKey(value)) {
return registry.get(value);
} else {
throw new IllegalArgumentException("Unknown handshake type " + value);
}
}
@Override
public int compareTo(HandshakeType o) {
return value().compareTo(o.value());
}
}

View File

@@ -1,6 +1,5 @@
package ru.serega6531.packmate.service.optimization.tls.records; package ru.serega6531.packmate.service.optimization.tls.records;
import org.pcap4j.packet.IllegalRawDataException;
import org.pcap4j.util.ByteArrays; import org.pcap4j.util.ByteArrays;
public class ChangeCipherSpecRecord extends TlsRecord { public class ChangeCipherSpecRecord extends TlsRecord {
@@ -8,7 +7,6 @@ public class ChangeCipherSpecRecord extends TlsRecord {
private byte changeCipherSpecMessage; private byte changeCipherSpecMessage;
public static ChangeCipherSpecRecord newInstance(byte[] rawData, int offset, int length) { public static ChangeCipherSpecRecord newInstance(byte[] rawData, int offset, int length) {
ByteArrays.validateBounds(rawData, offset, length);
return new ChangeCipherSpecRecord(rawData, offset); return new ChangeCipherSpecRecord(rawData, offset);
} }
@@ -16,4 +14,8 @@ public class ChangeCipherSpecRecord extends TlsRecord {
this.changeCipherSpecMessage = ByteArrays.getByte(rawData, offset); this.changeCipherSpecMessage = ByteArrays.getByte(rawData, offset);
} }
@Override
public String toString() {
return " Change Cipher Spec Message: " + changeCipherSpecMessage;
}
} }

View File

@@ -0,0 +1,55 @@
package ru.serega6531.packmate.service.optimization.tls.records;
import org.pcap4j.util.ByteArrays;
import ru.serega6531.packmate.service.optimization.tls.numbers.HandshakeType;
import ru.serega6531.packmate.service.optimization.tls.records.handshakes.ClientHelloHandshakeRecordContent;
import ru.serega6531.packmate.service.optimization.tls.records.handshakes.HandshakeRecordContent;
import static org.pcap4j.util.ByteArrays.BYTE_SIZE_IN_BYTES;
public class HandshakeRecord extends TlsRecord {
private static final int HANDSHAKE_TYPE_OFFSET = 0;
private HandshakeType handshakeType;
private HandshakeRecordContent content;
public static HandshakeRecord newInstance(byte[] rawData, int offset, int length) {
return new HandshakeRecord(rawData, offset, length);
}
private HandshakeRecord(byte[] rawData, int offset, int length) {
this.handshakeType = HandshakeType.getInstance(ByteArrays.getByte(rawData, HANDSHAKE_TYPE_OFFSET + offset));
if (handshakeType == HandshakeType.HELLO_REQUEST) {
} else if (handshakeType == HandshakeType.CLIENT_HELLO) {
this.content = ClientHelloHandshakeRecordContent.newInstance(
rawData, offset + BYTE_SIZE_IN_BYTES, length);
} else if (handshakeType == HandshakeType.SERVER_HELLO) {
} else if (handshakeType == HandshakeType.CERTIFICATE) {
} else if (handshakeType == HandshakeType.SERVER_KEY_EXCHANGE) {
} else if (handshakeType == HandshakeType.CERTIFICATE_REQUEST) {
} else if (handshakeType == HandshakeType.SERVER_HELLO_DONE) {
} else if (handshakeType == HandshakeType.CERTIFICATE_VERIFY) {
} else if (handshakeType == HandshakeType.CLIENT_KEY_EXCHANGE) {
} else if (handshakeType == HandshakeType.FINISHED) {
} else {
throw new IllegalArgumentException("Unknown handshake type " + handshakeType);
}
}
@Override
public String toString() {
return " Handshake type: " + handshakeType + "\n" +
content.toString();
}
}

View File

@@ -0,0 +1,103 @@
package ru.serega6531.packmate.service.optimization.tls.records.handshakes;
import org.pcap4j.util.ByteArrays;
import ru.serega6531.packmate.service.optimization.tls.numbers.CipherSuite;
import ru.serega6531.packmate.service.optimization.tls.numbers.CompressionMethod;
import ru.serega6531.packmate.service.optimization.tls.numbers.ExtensionType;
import ru.serega6531.packmate.service.optimization.tls.numbers.TlsVersion;
import ru.serega6531.packmate.utils.BytesUtils;
import java.util.ArrayList;
import java.util.List;
import static org.pcap4j.util.ByteArrays.BYTE_SIZE_IN_BYTES;
import static org.pcap4j.util.ByteArrays.SHORT_SIZE_IN_BYTES;
public class ClientHelloHandshakeRecordContent implements HandshakeRecordContent {
private static final int LENGTH_OFFSET = 0;
private static final int VERSION_OFFSET = LENGTH_OFFSET + 3;
private static final int RANDOM_OFFSET = VERSION_OFFSET + SHORT_SIZE_IN_BYTES;
private static final int SESSION_ID_LENGTH_OFFSET = RANDOM_OFFSET + 32;
private static final int SESSION_ID_OFFSET = SESSION_ID_LENGTH_OFFSET + BYTE_SIZE_IN_BYTES;
private static final int CIPHER_SUITES_LENGTH_OFFSET = SESSION_ID_OFFSET; // + sessionIdLength
private static final int CIPHER_SUITE_OFFSET =
CIPHER_SUITES_LENGTH_OFFSET + SHORT_SIZE_IN_BYTES; // + sessionIdLength + SHORT_SIZE_IN_BYTES*i
private static final int COMPRESSION_METHODS_LENGTH_OFFSET = CIPHER_SUITE_OFFSET; // + sessionIdLength + cipherSuitesLength
private static final int COMPRESSION_METHOD_OFFSET =
COMPRESSION_METHODS_LENGTH_OFFSET + BYTE_SIZE_IN_BYTES; // + sessionIdLength + cipherSuitesLength + BYTE_SIZE_IN_BYTES*i
private static final int EXTENSIONS_LENTH_OFFSET =
COMPRESSION_METHOD_OFFSET; // + sessionIdLength + cipherSuitesLength + compressionMethodsLength
private static final int EXTENSION_OFFSET = COMPRESSION_METHOD_OFFSET + SHORT_SIZE_IN_BYTES;
private int length; // 3 bytes
private TlsVersion version;
private byte[] random = new byte[32];
private byte sessionIdLength;
private byte[] sessionId;
private short cipherSuitesLength;
private List<CipherSuite> cipherSuites;
private byte compressionMethodsLength;
private List<CompressionMethod> compressionMethods;
private short extensionsLength;
public static ClientHelloHandshakeRecordContent newInstance(byte[] rawData, int offset, int length) {
return new ClientHelloHandshakeRecordContent(rawData, offset, length);
}
private ClientHelloHandshakeRecordContent(byte[] rawData, int offset, int length) {
this.length = BytesUtils.getThreeBytesInt(rawData, LENGTH_OFFSET + offset);
this.version = TlsVersion.getInstance(ByteArrays.getShort(rawData, VERSION_OFFSET + offset));
System.arraycopy(rawData, RANDOM_OFFSET + offset, random, 0, 32);
this.sessionIdLength = ByteArrays.getByte(rawData, SESSION_ID_LENGTH_OFFSET + offset);
this.sessionId = new byte[sessionIdLength];
if (sessionIdLength != 0) {
System.arraycopy(rawData, SESSION_ID_OFFSET + offset, sessionId, 0, sessionIdLength);
}
this.cipherSuitesLength = ByteArrays.getShort(rawData, CIPHER_SUITES_LENGTH_OFFSET + sessionIdLength + offset);
int cipherSuitesAmount = cipherSuitesLength / SHORT_SIZE_IN_BYTES;
this.cipherSuites = new ArrayList<>(cipherSuitesAmount);
for (int i = 0; i < cipherSuitesAmount; i++) {
this.cipherSuites.add(CipherSuite.getInstance(ByteArrays.getShort(rawData,
CIPHER_SUITE_OFFSET + SHORT_SIZE_IN_BYTES * i + sessionIdLength + offset)));
}
this.compressionMethodsLength = ByteArrays.getByte(rawData,
COMPRESSION_METHODS_LENGTH_OFFSET + cipherSuitesLength + sessionIdLength + offset);
this.compressionMethods = new ArrayList<>(compressionMethodsLength);
for (byte i = 0; i < compressionMethodsLength; i++) {
this.compressionMethods.add(CompressionMethod.getInstance(ByteArrays.getByte(rawData,
COMPRESSION_METHOD_OFFSET + BYTE_SIZE_IN_BYTES * i + sessionIdLength + cipherSuitesLength + offset)));
}
this.extensionsLength = ByteArrays.getShort(rawData,
COMPRESSION_METHOD_OFFSET + compressionMethodsLength + sessionIdLength + cipherSuitesLength + offset);
int cursor = EXTENSION_OFFSET + compressionMethodsLength + sessionIdLength + cipherSuitesLength + offset;
int extensionsEnd = cursor + extensionsLength;
while (cursor < extensionsEnd) {
ExtensionType extensionType = ExtensionType.getInstance(ByteArrays.getShort(rawData, cursor));
cursor += SHORT_SIZE_IN_BYTES;
short extensionLength = ByteArrays.getShort(rawData, cursor);
cursor += SHORT_SIZE_IN_BYTES;
cursor += extensionLength;
//TODO
}
}
@Override
public String toString() {
return " Handshake length: " + length + "\n" +
" TLS version: " + version + "\n" +
" Client random: " + ByteArrays.toHexString(random, "") + "\n" +
" Session id: " + (sessionIdLength > 0 ? ByteArrays.toHexString(sessionId, "") : "null") + "\n" +
" Cipher suites: " + cipherSuites.toString() + "\n" +
" Compression methods: " + compressionMethods.toString() + "\n" +
" Extensions: TODO";
}
}

View File

@@ -0,0 +1,4 @@
package ru.serega6531.packmate.service.optimization.tls.records.handshakes;
public interface HandshakeRecordContent {
}

View File

@@ -1,12 +1,18 @@
package ru.serega6531.packmate.utils; package ru.serega6531.packmate.utils;
import lombok.experimental.UtilityClass; import lombok.experimental.UtilityClass;
import org.pcap4j.util.ByteArrays;
import java.nio.ByteOrder;
import static java.nio.ByteOrder.LITTLE_ENDIAN;
import static org.pcap4j.util.ByteArrays.BYTE_SIZE_IN_BITS;
@UtilityClass @UtilityClass
public class BytesUtils { public class BytesUtils {
/** /**
* @param array где ищем * @param array где ищем
* @param target что ищем * @param target что ищем
*/ */
public int indexOf(byte[] array, byte[] target, int start, int end) { public int indexOf(byte[] array, byte[] target, int start, int end) {
@@ -27,16 +33,16 @@ public class BytesUtils {
} }
/** /**
* @param array где ищем * @param array где ищем
* @param target что ищем * @param target что ищем
*/ */
public boolean endsWith(byte[] array, byte[] target) { public boolean endsWith(byte[] array, byte[] target) {
if(array.length < target.length) { if (array.length < target.length) {
return false; return false;
} }
for (int i = 0; i < target.length; i++) { for (int i = 0; i < target.length; i++) {
if(array[array.length - target.length + i] != target[i]) { if (array[array.length - target.length + i] != target[i]) {
return false; return false;
} }
} }
@@ -44,4 +50,37 @@ public class BytesUtils {
return true; return true;
} }
/**
* @param array array
* @param offset offset
* @return int value.
*/
public int getThreeBytesInt(byte[] array, int offset) {
return getThreeBytesInt(array, offset, ByteOrder.BIG_ENDIAN);
}
/**
* @param array array
* @param offset offset
* @param bo bo
* @return int value.
*/
public int getThreeBytesInt(byte[] array, int offset, ByteOrder bo) {
ByteArrays.validateBounds(array, offset, 3);
if (bo == null) {
throw new NullPointerException(" bo: null");
}
if (bo.equals(LITTLE_ENDIAN)) {
return ((0xFF & array[offset + 2]) << (BYTE_SIZE_IN_BITS * 2))
| ((0xFF & array[offset + 1]) << (BYTE_SIZE_IN_BITS * 1))
| ((0xFF & array[offset]));
} else {
return ((0xFF & array[offset]) << (BYTE_SIZE_IN_BITS * 2))
| ((0xFF & array[offset + 1]) << (BYTE_SIZE_IN_BITS * 1))
| ((0xFF & array[offset + 2]));
}
}
} }