diff --git a/TLS-Core/src/main/java/de/rub/nds/tlsattacker/core/layer/impl/RecordLayer.java b/TLS-Core/src/main/java/de/rub/nds/tlsattacker/core/layer/impl/RecordLayer.java index bb206027c5..154a21c99b 100644 --- a/TLS-Core/src/main/java/de/rub/nds/tlsattacker/core/layer/impl/RecordLayer.java +++ b/TLS-Core/src/main/java/de/rub/nds/tlsattacker/core/layer/impl/RecordLayer.java @@ -35,6 +35,7 @@ import de.rub.nds.tlsattacker.core.record.preparator.RecordPreparator; import de.rub.nds.tlsattacker.core.record.serializer.RecordSerializer; import de.rub.nds.tlsattacker.core.state.Context; +import de.rub.nds.tlsattacker.core.tcp.TcpSegmentConfiguration; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; @@ -110,7 +111,15 @@ public LayerProcessingResult sendConfiguration() throws IOException { RecordSerializer serializer = record.getRecordSerializer(); byte[] serializedMessage = serializer.serialize(); record.setCompleteRecordBytes(serializedMessage); - getLowerLayer().sendData(null, record.getCompleteRecordBytes().getValue()); + + // Check if TCP segmentation is configured for this record + if (record.getTcpSegmentConfiguration() != null) { + sendWithTcpSegmentation( + record.getCompleteRecordBytes().getValue(), + record.getTcpSegmentConfiguration()); + } else { + getLowerLayer().sendData(null, record.getCompleteRecordBytes().getValue()); + } addProducedContainer(record); } } @@ -299,6 +308,50 @@ public RecordCipher getDecryptorCipher() { return decryptor.getRecordMostRecentCipher(); } + /** + * Sends data with TCP segmentation according to the provided configuration. + * + * @param data The data to send + * @param segmentConfig The TCP segmentation configuration + * @throws IOException If the data cannot be sent + */ + private void sendWithTcpSegmentation(byte[] data, TcpSegmentConfiguration segmentConfig) + throws IOException { + if (segmentConfig.getSegments() == null || segmentConfig.getSegments().isEmpty()) { + // No segments defined, send as single packet + getLowerLayer().sendData(null, data); + return; + } + + for (TcpSegmentConfiguration.TcpSegment segment : segmentConfig.getSegments()) { + int offset = segment.getOffset() != null ? segment.getOffset() : 0; + int length = segment.getLength() != null ? segment.getLength() : data.length - offset; + + // Ensure we don't go out of bounds + if (offset >= data.length) { + LOGGER.warn("TCP segment offset {} exceeds data length {}", offset, data.length); + continue; + } + + int actualLength = Math.min(length, data.length - offset); + byte[] segmentData = new byte[actualLength]; + System.arraycopy(data, offset, segmentData, 0, actualLength); + + // Send the segment + getLowerLayer().sendData(null, segmentData); + + // Add delay between segments if configured + if (segmentConfig.getSegmentDelay() != null && segmentConfig.getSegmentDelay() > 0) { + try { + Thread.sleep(segmentConfig.getSegmentDelay()); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + LOGGER.warn("TCP segment delay interrupted", e); + } + } + } + } + public void updateCompressor() { compressor.setMethod(context.getChooser().getSelectedCompressionMethod()); } diff --git a/TLS-Core/src/main/java/de/rub/nds/tlsattacker/core/record/Record.java b/TLS-Core/src/main/java/de/rub/nds/tlsattacker/core/record/Record.java index 703f719582..c090e7a450 100644 --- a/TLS-Core/src/main/java/de/rub/nds/tlsattacker/core/record/Record.java +++ b/TLS-Core/src/main/java/de/rub/nds/tlsattacker/core/record/Record.java @@ -30,8 +30,10 @@ import de.rub.nds.tlsattacker.core.record.preparator.RecordPreparator; import de.rub.nds.tlsattacker.core.record.serializer.RecordSerializer; import de.rub.nds.tlsattacker.core.state.Context; +import de.rub.nds.tlsattacker.core.tcp.TcpSegmentConfiguration; import jakarta.xml.bind.annotation.XmlAccessType; import jakarta.xml.bind.annotation.XmlAccessorType; +import jakarta.xml.bind.annotation.XmlElement; import jakarta.xml.bind.annotation.XmlRootElement; import jakarta.xml.bind.annotation.XmlTransient; import java.io.InputStream; @@ -92,6 +94,10 @@ public class Record extends ModifiableVariableHolder implements DataContainer { private RecordCryptoComputations computations; + /** TCP segmentation configuration for this record */ + @XmlElement(name = "tcpSegmentation") + private TcpSegmentConfiguration tcpSegmentConfiguration; + public Record(Config config) { this.maxRecordLengthConfig = config.getDefaultMaxRecordData(); } @@ -313,6 +319,14 @@ public void prepareComputations() { } } + public TcpSegmentConfiguration getTcpSegmentConfiguration() { + return tcpSegmentConfiguration; + } + + public void setTcpSegmentConfiguration(TcpSegmentConfiguration tcpSegmentConfiguration) { + this.tcpSegmentConfiguration = tcpSegmentConfiguration; + } + @Override public String toString() { diff --git a/TLS-Core/src/main/java/de/rub/nds/tlsattacker/core/tcp/TcpSegmentConfiguration.java b/TLS-Core/src/main/java/de/rub/nds/tlsattacker/core/tcp/TcpSegmentConfiguration.java new file mode 100644 index 0000000000..cd4758c586 --- /dev/null +++ b/TLS-Core/src/main/java/de/rub/nds/tlsattacker/core/tcp/TcpSegmentConfiguration.java @@ -0,0 +1,97 @@ +/* + * TLS-Attacker - A Modular Penetration Testing Framework for TLS + * + * Copyright 2014-2023 Ruhr University Bochum, Paderborn University, Technology Innovation Institute, and Hackmanit GmbH + * + * Licensed under Apache License, Version 2.0 + * http://www.apache.org/licenses/LICENSE-2.0.txt + */ +package de.rub.nds.tlsattacker.core.tcp; + +import jakarta.xml.bind.annotation.XmlAccessType; +import jakarta.xml.bind.annotation.XmlAccessorType; +import jakarta.xml.bind.annotation.XmlElement; +import jakarta.xml.bind.annotation.XmlRootElement; +import java.io.Serializable; +import java.util.LinkedList; +import java.util.List; + +/** + * Configuration for TCP segmentation. Allows specifying how data should be split across TCP + * segments. + */ +@XmlRootElement +@XmlAccessorType(XmlAccessType.FIELD) +public class TcpSegmentConfiguration implements Serializable { + + @XmlElement(name = "segment") + private List segments = new LinkedList<>(); + + /** Default TCP segment delay in milliseconds between segments */ + @XmlElement(name = "segmentDelay") + private Integer segmentDelay = 10; + + public TcpSegmentConfiguration() {} + + public TcpSegmentConfiguration(List segments) { + this.segments = segments; + } + + public List getSegments() { + return segments; + } + + public void setSegments(List segments) { + this.segments = segments; + } + + public void addSegment(TcpSegment segment) { + if (segments == null) { + segments = new LinkedList<>(); + } + segments.add(segment); + } + + public Integer getSegmentDelay() { + return segmentDelay; + } + + public void setSegmentDelay(Integer segmentDelay) { + this.segmentDelay = segmentDelay; + } + + /** Represents a single TCP segment with offset and length */ + @XmlRootElement + @XmlAccessorType(XmlAccessType.FIELD) + public static class TcpSegment implements Serializable { + + @XmlElement(name = "offset") + private Integer offset; + + @XmlElement(name = "length") + private Integer length; + + public TcpSegment() {} + + public TcpSegment(Integer offset, Integer length) { + this.offset = offset; + this.length = length; + } + + public Integer getOffset() { + return offset; + } + + public void setOffset(Integer offset) { + this.offset = offset; + } + + public Integer getLength() { + return length; + } + + public void setLength(Integer length) { + this.length = length; + } + } +} diff --git a/TLS-Core/src/test/java/de/rub/nds/tlsattacker/core/tcp/TcpSegmentConfigurationTest.java b/TLS-Core/src/test/java/de/rub/nds/tlsattacker/core/tcp/TcpSegmentConfigurationTest.java new file mode 100644 index 0000000000..9ea8bdfab6 --- /dev/null +++ b/TLS-Core/src/test/java/de/rub/nds/tlsattacker/core/tcp/TcpSegmentConfigurationTest.java @@ -0,0 +1,100 @@ +/* + * TLS-Attacker - A Modular Penetration Testing Framework for TLS + * + * Copyright 2014-2023 Ruhr University Bochum, Paderborn University, Technology Innovation Institute, and Hackmanit GmbH + * + * Licensed under Apache License, Version 2.0 + * http://www.apache.org/licenses/LICENSE-2.0.txt + */ +package de.rub.nds.tlsattacker.core.tcp; + +import static org.junit.jupiter.api.Assertions.*; + +import de.rub.nds.tlsattacker.core.tcp.TcpSegmentConfiguration.TcpSegment; +import jakarta.xml.bind.JAXBContext; +import jakarta.xml.bind.JAXBException; +import jakarta.xml.bind.Marshaller; +import jakarta.xml.bind.Unmarshaller; +import java.io.StringReader; +import java.io.StringWriter; +import org.junit.jupiter.api.Test; + +public class TcpSegmentConfigurationTest { + + @Test + public void testTcpSegmentConfiguration() { + TcpSegmentConfiguration config = new TcpSegmentConfiguration(); + + // Test adding segments + config.addSegment(new TcpSegment(0, 3)); + config.addSegment(new TcpSegment(3, 10)); + config.setSegmentDelay(15); + + assertEquals(2, config.getSegments().size()); + assertEquals(0, config.getSegments().get(0).getOffset().intValue()); + assertEquals(3, config.getSegments().get(0).getLength().intValue()); + assertEquals(3, config.getSegments().get(1).getOffset().intValue()); + assertEquals(10, config.getSegments().get(1).getLength().intValue()); + assertEquals(15, config.getSegmentDelay().intValue()); + } + + @Test + public void testTcpSegmentConfigurationSerialization() throws JAXBException { + // Create configuration + TcpSegmentConfiguration config = new TcpSegmentConfiguration(); + config.addSegment(new TcpSegment(0, 5)); + config.addSegment(new TcpSegment(5, null)); + config.setSegmentDelay(20); + + // Serialize to XML + JAXBContext context = JAXBContext.newInstance(TcpSegmentConfiguration.class); + Marshaller marshaller = context.createMarshaller(); + marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); + + StringWriter writer = new StringWriter(); + marshaller.marshal(config, writer); + String xml = writer.toString(); + + // Verify XML contains expected elements + assertTrue(xml.contains("")); + assertTrue(xml.contains("")); + assertTrue(xml.contains("0")); + assertTrue(xml.contains("5")); + assertTrue(xml.contains("5")); + assertTrue(xml.contains("20")); + + // Deserialize from XML + Unmarshaller unmarshaller = context.createUnmarshaller(); + TcpSegmentConfiguration deserialized = + (TcpSegmentConfiguration) unmarshaller.unmarshal(new StringReader(xml)); + + // Verify deserialized object + assertNotNull(deserialized); + assertEquals(2, deserialized.getSegments().size()); + assertEquals(0, deserialized.getSegments().get(0).getOffset().intValue()); + assertEquals(5, deserialized.getSegments().get(0).getLength().intValue()); + assertEquals(5, deserialized.getSegments().get(1).getOffset().intValue()); + assertNull(deserialized.getSegments().get(1).getLength()); + assertEquals(20, deserialized.getSegmentDelay().intValue()); + } + + @Test + public void testEmptyConfiguration() { + TcpSegmentConfiguration config = new TcpSegmentConfiguration(); + assertNotNull(config.getSegments()); + assertTrue(config.getSegments().isEmpty()); + assertEquals(10, config.getSegmentDelay().intValue()); // Default value + } + + @Test + public void testTcpSegment() { + TcpSegment segment = new TcpSegment(10, 20); + assertEquals(10, segment.getOffset().intValue()); + assertEquals(20, segment.getLength().intValue()); + + segment.setOffset(15); + segment.setLength(25); + assertEquals(15, segment.getOffset().intValue()); + assertEquals(25, segment.getLength().intValue()); + } +} diff --git a/docs/TCP_SEGMENTATION.md b/docs/TCP_SEGMENTATION.md new file mode 100644 index 0000000000..9352697f6c --- /dev/null +++ b/docs/TCP_SEGMENTATION.md @@ -0,0 +1,139 @@ +# TCP Segmentation Feature + +## Overview + +TLS-Attacker now supports fine-grained control over TCP segmentation, allowing you to split TLS records across multiple TCP segments. This feature enables testing of implementations' handling of fragmented TLS records at the TCP layer. + +## Use Cases + +- Testing TLS implementations' robustness against fragmented records +- Simulating network conditions where records are split across packets +- Security testing for timing and state handling issues +- Compliance testing for proper reassembly of fragmented data + +## Configuration + +TCP segmentation is configured per record using the `` element within a `` configuration. + +### Basic Structure + +```xml + + + + 0 + 3 + + + 3 + + 10 + + +``` + +### Parameters + +- **segment**: Defines a single TCP segment + - **offset**: Starting byte position in the record (0-based) + - **length**: Number of bytes to include in this segment (optional, defaults to remaining bytes) +- **segmentDelay**: Delay in milliseconds between sending segments (optional, default: 10ms) + +## Examples + +### Example 1: Split Record Header + +Split a TLS record header (5 bytes) across two TCP segments: + +```xml + + + + + + + + + + 0 + 3 + + + + 3 + + 10 + + + + +``` + +### Example 2: Multiple Segments + +Split a record into three segments: + +```xml + + + + + 0 + 5 + + + + 5 + 10 + + + + 15 + + 5 + + +``` + +### Example 3: Programmatic Usage + +```java +// Create a record with TCP segmentation +Record record = new Record(); +TcpSegmentConfiguration segmentConfig = new TcpSegmentConfiguration(); + +// Split at byte 3 +segmentConfig.addSegment(new TcpSegment(0, 3)); +segmentConfig.addSegment(new TcpSegment(3, null)); +segmentConfig.setSegmentDelay(10); + +record.setTcpSegmentConfiguration(segmentConfig); + +// Use in SendAction +SendAction sendAction = new SendAction(message); +sendAction.setConfiguredRecords(List.of(record)); +``` + +## Implementation Details + +- Segmentation is applied after record serialization +- Each segment is sent as a separate TCP packet +- Segments are sent in order with configured delays +- Out-of-bounds segments are skipped with a warning +- If no segmentation is configured, records are sent normally + +## Transport Handler Support + +TCP segmentation works with all TCP-based transport handlers: +- TCP +- TCP_TIMING +- TCP_NO_DELAY +- TCP_FRAGMENTATION (different feature - splits all data uniformly) + +## Testing + +The feature includes comprehensive unit tests in `TcpSegmentationTest.java` and integration test examples in `TcpSegmentationIT.java`. + +## Complete Example + +See `resources/examples/tcp_segmentation_example.xml` for a complete workflow demonstrating various TCP segmentation scenarios. diff --git a/resources/examples/tcp_segmentation_example.xml b/resources/examples/tcp_segmentation_example.xml new file mode 100644 index 0000000000..57b2c5cf16 --- /dev/null +++ b/resources/examples/tcp_segmentation_example.xml @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + 0 + 3 + + + 3 + + 10 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + 5 + + + 5 + 10 + + + 15 + + 5 + + + + + + + + + + + + + + + + + + + + + + 0 + 2 + + + 2 + + 20 + + + + + + + + + + + + + \ No newline at end of file