Работа над разбором TLS пакетов
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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]";
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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]";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user