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

This commit is contained in:
serega6531
2020-04-19 01:02:05 +03:00
parent 10dd6005a2
commit f71fab49a2
9 changed files with 230 additions and 61 deletions

View File

@@ -6,6 +6,7 @@ import org.pcap4j.packet.Packet;
import org.pcap4j.util.ByteArrays;
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.records.ApplicationDataRecord;
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;
@@ -51,11 +52,30 @@ public class TlsPacket extends AbstractPacket {
return header;
}
@Override
public Packet getPayload() {
return payload;
}
@Override
public Builder getBuilder() {
return new Builder(this);
}
@Override
protected String buildString() {
StringBuilder sb = new StringBuilder(getHeader().toString());
TlsPacket p = (TlsPacket) getPayload();
if (p != null) {
sb.append('\n');
sb.append(p.toString());
}
return sb.toString();
}
public static final class TlsHeader extends AbstractHeader {
private static final int CONTENT_TYPE_OFFSET = 0;
@@ -65,7 +85,7 @@ public class TlsPacket extends AbstractPacket {
private ContentType contentType;
private TlsVersion version;
private short length;
private short recordLength;
private TlsRecord record;
private TlsHeader(Builder builder) {
@@ -76,16 +96,16 @@ public class TlsPacket extends AbstractPacket {
//TODO check length
this.contentType = ContentType.getInstance(ByteArrays.getByte(rawData, CONTENT_TYPE_OFFSET + offset));
this.version = TlsVersion.getInstance(ByteArrays.getShort(rawData, VERSION_OFFSET + offset));
this.length = ByteArrays.getShort(rawData, LENGTH_OFFSET + offset);
this.recordLength = ByteArrays.getShort(rawData, LENGTH_OFFSET + offset);
if (contentType == ContentType.HANDSHAKE) {
this.record = HandshakeRecord.newInstance(rawData, offset + RECORD_OFFSET, length);
this.record = HandshakeRecord.newInstance(rawData, offset + RECORD_OFFSET, recordLength);
} else if (contentType == ContentType.CHANGE_CIPHER_SPEC) {
this.record = ChangeCipherSpecRecord.newInstance(rawData, offset + RECORD_OFFSET, length);
this.record = ChangeCipherSpecRecord.newInstance(rawData, offset + RECORD_OFFSET, recordLength);
} else if (contentType == ContentType.APPLICATION_DATA) {
this.record = ApplicationDataRecord.newInstance(rawData, offset + RECORD_OFFSET, recordLength);
} else if (contentType == ContentType.ALERT) {
//TODO
} else {
throw new IllegalArgumentException("Unknown content type: " + contentType);
}
@@ -96,13 +116,14 @@ public class TlsPacket extends AbstractPacket {
List<byte[]> rawFields = new ArrayList<>();
rawFields.add(new byte[]{contentType.value()});
rawFields.add(ByteArrays.toByteArray(version.value()));
rawFields.add(ByteArrays.toByteArray(length));
rawFields.add(ByteArrays.toByteArray(recordLength));
//TODO
return rawFields;
}
@Override
public int length() {
return RECORD_OFFSET + length;
return RECORD_OFFSET + recordLength;
}
@Override

View File

@@ -0,0 +1,37 @@
package ru.serega6531.packmate.service.optimization.tls.extensions;
import ru.serega6531.packmate.service.optimization.tls.numbers.ExtensionType;
public class TlsExtension {
private ExtensionType type;
private short length;
private byte[] data; // TODO create packets for each extension
public TlsExtension(ExtensionType type, short length, byte[] data) {
this.type = type;
this.length = length;
this.data = data;
}
public ExtensionType getType() {
return type;
}
public short getLength() {
return length;
}
public byte[] getData() {
return data;
}
@Override
public String toString() {
if (data.length == 0) {
return type.name();
}
return type.name() + " [" + data.length + " bytes]";
}
}

View File

@@ -8,11 +8,13 @@ import java.util.Map;
public class ExtensionType extends NamedNumber<Short, ExtensionType> {
public static final ExtensionType RESERVED_GREASE = new ExtensionType((short) 14906, "Reserved (GREASE)");
public static final ExtensionType PADDING = new ExtensionType((short) 21, "Padding");
private static final Map<Short, ExtensionType> registry = new HashMap<>();
static {
registry.put(RESERVED_GREASE.value(), RESERVED_GREASE);
registry.put(PADDING.value(), PADDING);
//TODO add all
}

View File

@@ -0,0 +1,21 @@
package ru.serega6531.packmate.service.optimization.tls.records;
public class ApplicationDataRecord extends TlsRecord {
private byte[] data;
public static ApplicationDataRecord newInstance(byte[] rawData, int offset, int length) {
return new ApplicationDataRecord(rawData, offset, length);
}
public ApplicationDataRecord(byte[] rawData, int offset, int length) {
data = new byte[length];
System.arraycopy(rawData, offset, data, 0, length);
}
@Override
public String toString() {
return " Encrypted data: [" + data.length + " bytes]";
}
}

View File

@@ -4,12 +4,14 @@ 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 ru.serega6531.packmate.service.optimization.tls.records.handshakes.ServerHelloHandshakeRecordContent;
import static org.pcap4j.util.ByteArrays.BYTE_SIZE_IN_BYTES;
public class HandshakeRecord extends TlsRecord {
private static final int HANDSHAKE_TYPE_OFFSET = 0;
private static final int CONTENT_OFFSET = HANDSHAKE_TYPE_OFFSET + BYTE_SIZE_IN_BYTES;
private HandshakeType handshakeType;
private HandshakeRecordContent content;
@@ -25,9 +27,10 @@ public class HandshakeRecord extends TlsRecord {
} else if (handshakeType == HandshakeType.CLIENT_HELLO) {
this.content = ClientHelloHandshakeRecordContent.newInstance(
rawData, offset + BYTE_SIZE_IN_BYTES, length);
rawData, offset + CONTENT_OFFSET);
} else if (handshakeType == HandshakeType.SERVER_HELLO) {
this.content = ServerHelloHandshakeRecordContent.newInstance(
rawData, offset + CONTENT_OFFSET);
} else if (handshakeType == HandshakeType.CERTIFICATE) {
} else if (handshakeType == HandshakeType.SERVER_KEY_EXCHANGE) {

View File

@@ -3,9 +3,6 @@ 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;
@@ -13,48 +10,29 @@ 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 {
public class ClientHelloHandshakeRecordContent extends HelloHandshakeRecordContent {
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_SUITES_LENGTH_OFFSET = HelloHandshakeRecordContent.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 =
private static final int EXTENSIONS_LENGTH_OFFSET =
COMPRESSION_METHOD_OFFSET; // + sessionIdLength + cipherSuitesLength + compressionMethodsLength
private static final int EXTENSION_OFFSET = COMPRESSION_METHOD_OFFSET + SHORT_SIZE_IN_BYTES;
private static final int EXTENSIONS_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);
public static ClientHelloHandshakeRecordContent newInstance(byte[] rawData, int offset) {
return new ClientHelloHandshakeRecordContent(rawData, offset);
}
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);
}
private ClientHelloHandshakeRecordContent(byte[] rawData, int offset) {
readCommonPart(rawData, offset);
this.cipherSuitesLength = ByteArrays.getShort(rawData, CIPHER_SUITES_LENGTH_OFFSET + sessionIdLength + offset);
int cipherSuitesAmount = cipherSuitesLength / SHORT_SIZE_IN_BYTES;
@@ -75,29 +53,15 @@ public class ClientHelloHandshakeRecordContent implements HandshakeRecordContent
}
this.extensionsLength = ByteArrays.getShort(rawData,
COMPRESSION_METHOD_OFFSET + compressionMethodsLength + sessionIdLength + cipherSuitesLength + offset);
EXTENSIONS_LENGTH_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
}
readExtensions(rawData, EXTENSIONS_OFFSET + compressionMethodsLength + sessionIdLength + cipherSuitesLength + offset);
}
@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" +
return super.toString() + "\n" +
" Cipher suites: " + cipherSuites.toString() + "\n" +
" Compression methods: " + compressionMethods.toString() + "\n" +
" Extensions: TODO";
" Compression methods: " + compressionMethods.toString();
}
}

View File

@@ -0,0 +1,73 @@
package ru.serega6531.packmate.service.optimization.tls.records.handshakes;
import org.pcap4j.util.ByteArrays;
import ru.serega6531.packmate.service.optimization.tls.extensions.TlsExtension;
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 abstract class HelloHandshakeRecordContent 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;
protected static final int SESSION_ID_OFFSET = SESSION_ID_LENGTH_OFFSET + BYTE_SIZE_IN_BYTES;
protected int length; // 3 bytes
protected TlsVersion version;
protected byte[] random = new byte[32];
protected byte sessionIdLength;
protected byte[] sessionId;
protected short extensionsLength;
private List<TlsExtension> extensions;
protected void readCommonPart(byte[] rawData, int offset) {
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);
}
}
protected void readExtensions(byte[] rawData, int offset) {
extensions = new ArrayList<>(extensionsLength);
int cursor = 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;
byte[] extensionData = new byte[extensionLength];
System.arraycopy(rawData, cursor, extensionData, 0, extensionLength);
extensions.add(new TlsExtension(extensionType, extensionLength, extensionData));
cursor += extensionLength;
}
}
@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" +
" Extensions: " + extensions.toString();
}
}

View File

@@ -0,0 +1,44 @@
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 static org.pcap4j.util.ByteArrays.BYTE_SIZE_IN_BYTES;
import static org.pcap4j.util.ByteArrays.SHORT_SIZE_IN_BYTES;
public class ServerHelloHandshakeRecordContent extends HelloHandshakeRecordContent {
private static final int CIPHER_SUITE_OFFSET = HelloHandshakeRecordContent.SESSION_ID_OFFSET; // + sessionIdLength
private static final int COMPRESSION_METHOD_OFFSET = CIPHER_SUITE_OFFSET + SHORT_SIZE_IN_BYTES; // + sessionIdLength
private static final int EXTENSIONS_LENGTH_OFFSET = COMPRESSION_METHOD_OFFSET + BYTE_SIZE_IN_BYTES; // + sessionIdLength
private static final int EXTENSIONS_OFFSET = EXTENSIONS_LENGTH_OFFSET + SHORT_SIZE_IN_BYTES; // + sessionIdLength
private CipherSuite cipherSuite;
private CompressionMethod compressionMethod;
public static ServerHelloHandshakeRecordContent newInstance(byte[] rawData, int offset) {
return new ServerHelloHandshakeRecordContent(rawData, offset);
}
public ServerHelloHandshakeRecordContent(byte[] rawData, int offset) {
readCommonPart(rawData, offset);
this.cipherSuite = CipherSuite.getInstance(ByteArrays.getShort(rawData,
CIPHER_SUITE_OFFSET + sessionIdLength + offset));
this.compressionMethod = CompressionMethod.getInstance(ByteArrays.getByte(rawData,
COMPRESSION_METHOD_OFFSET + sessionIdLength + offset));
this.extensionsLength = ByteArrays.getShort(rawData,
EXTENSIONS_LENGTH_OFFSET + sessionIdLength + offset);
readExtensions(rawData, EXTENSIONS_OFFSET + sessionIdLength + offset);
}
@Override
public String toString() {
return super.toString() + "\n" +
" Cipher suite: " + cipherSuite.toString() + "\n" +
" Compression method: " + compressionMethod.toString();
}
}

View File

@@ -13,10 +13,14 @@ public class TlsPacketTest {
@Test
public void testHandshake() throws IOException, IllegalRawDataException {
List<Packet> packets = new PackmateDumpFileLoader("tls.pkmt").getPackets();
byte[] content = packets.get(0).getContent();
TlsPacket tlsPacket = TlsPacket.newPacket(content, 0, content.length);
System.out.println(tlsPacket.toString());
for (int i = 0; i < packets.size(); i++) {
Packet packet = packets.get(i);
System.out.println("Packet " + i + ", incoming: " + packet.isIncoming());
byte[] content = packet.getContent();
TlsPacket tlsPacket = TlsPacket.newPacket(content, 0, content.length);
System.out.println(tlsPacket.toString());
}
}
}