diff --git a/TLS-Core/src/main/java/de/rub/nds/tlsattacker/core/examples/DynamicHandshakeExample.java b/TLS-Core/src/main/java/de/rub/nds/tlsattacker/core/examples/DynamicHandshakeExample.java new file mode 100644 index 000000000..0e6a35897 --- /dev/null +++ b/TLS-Core/src/main/java/de/rub/nds/tlsattacker/core/examples/DynamicHandshakeExample.java @@ -0,0 +1,221 @@ +/* + * TLS-Attacker - A Modular Penetration Testing Framework for TLS + * + * Copyright 2014-2025 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.examples; + +import de.rub.nds.tlsattacker.core.config.Config; +import de.rub.nds.tlsattacker.core.connection.AliasedConnection; +import de.rub.nds.tlsattacker.core.connection.InboundConnection; +import de.rub.nds.tlsattacker.core.constants.ProtocolMessageType; +import de.rub.nds.tlsattacker.core.constants.RunningModeType; +import de.rub.nds.tlsattacker.core.exceptions.WorkflowExecutionException; +import de.rub.nds.tlsattacker.core.layer.context.TlsContext; +import de.rub.nds.tlsattacker.core.protocol.message.AlertMessage; +import de.rub.nds.tlsattacker.core.protocol.message.ChangeCipherSpecMessage; +import de.rub.nds.tlsattacker.core.protocol.message.ClientHelloMessage; +import de.rub.nds.tlsattacker.core.protocol.message.FinishedMessage; +import de.rub.nds.tlsattacker.core.protocol.message.HelloVerifyRequestMessage; +import de.rub.nds.tlsattacker.core.protocol.message.PskClientKeyExchangeMessage; +import de.rub.nds.tlsattacker.core.protocol.message.PskServerKeyExchangeMessage; +import de.rub.nds.tlsattacker.core.protocol.message.ServerHelloDoneMessage; +import de.rub.nds.tlsattacker.core.protocol.message.ServerHelloMessage; +import de.rub.nds.tlsattacker.core.state.State; +import de.rub.nds.tlsattacker.core.workflow.action.ReceiveAction; +import de.rub.nds.tlsattacker.core.workflow.action.ResetConnectionAction; +import de.rub.nds.tlsattacker.core.workflow.action.SendAction; +import de.rub.nds.tlsattacker.core.workflow.action.TlsAction; +import java.io.IOException; +import java.util.Arrays; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +/** + * Example demonstrating how to handle both session resumption and full handshake + * dynamically based on the ClientHello session ID. + * + * This implementation addresses the issue described in #195 where TLS-Attacker needs + * to support different handshake paths based on whether the client is attempting + * session resumption or a new full handshake. + */ +public class DynamicHandshakeExample { + + private static final Logger LOGGER = LogManager.getLogger(); + + public static void main(String[] args) { + Config config = Config.createConfig(); + config.setDefaultRunningMode(RunningModeType.SERVER); + + // Configure for PSK as shown in the issue + config.setDefaultSelectedCipherSuite("TLS_PSK_WITH_AES_128_CBC_SHA"); + config.setDefaultServerSupportedCipherSuites(Arrays.asList("TLS_PSK_WITH_AES_128_CBC_SHA")); + + // Add connection configuration + config.setDefaultConnections(Arrays.asList(new InboundConnection(4433, "server"))); + + try { + // Execute first handshake + State state = executeInitialHandshake(config); + + // Save session information + byte[] sessionId = state.getTlsContext().getServerSessionId(); + LOGGER.info("Initial handshake completed. Session ID: {}", bytesToHex(sessionId)); + + // Reset for second handshake + state = new State(config); + + // Execute second handshake with dynamic path selection + executeSecondHandshake(state, sessionId); + + } catch (Exception e) { + LOGGER.error("Error during handshake execution", e); + } + } + + /** + * Executes the initial full handshake + */ + private static State executeInitialHandshake(Config config) throws WorkflowExecutionException, IOException { + State state = new State(config); + TlsContext context = state.getTlsContext(); + + LOGGER.info("Starting initial full handshake..."); + + // Execute initial handshake actions + executeAction(new ReceiveAction(new ClientHelloMessage()), state); + executeAction(new SendAction(new HelloVerifyRequestMessage()), state); + executeAction(new ReceiveAction(new ClientHelloMessage()), state); + executeAction(new SendAction(new ServerHelloMessage()), state); + executeAction(new SendAction(new PskServerKeyExchangeMessage()), state); + executeAction(new SendAction(new ServerHelloDoneMessage()), state); + executeAction(new ReceiveAction(new PskClientKeyExchangeMessage()), state); + executeAction(new ReceiveAction(new ChangeCipherSpecMessage()), state); + executeAction(new ReceiveAction(new FinishedMessage()), state); + executeAction(new SendAction(new ChangeCipherSpecMessage()), state); + executeAction(new SendAction(new FinishedMessage()), state); + executeAction(new ReceiveAction(new AlertMessage()), state); + executeAction(new ResetConnectionAction(), state); + + return state; + } + + /** + * Executes the second handshake with dynamic path selection based on session ID + */ + private static void executeSecondHandshake(State state, byte[] expectedSessionId) + throws WorkflowExecutionException, IOException { + + LOGGER.info("Starting second handshake..."); + + // Receive first ClientHello + ReceiveAction receiveClientHello = new ReceiveAction(new ClientHelloMessage()); + executeAction(receiveClientHello, state); + + // Check if ClientHello was received + ClientHelloMessage clientHello = (ClientHelloMessage) receiveClientHello.getReceivedMessages().stream() + .filter(msg -> msg instanceof ClientHelloMessage) + .findFirst() + .orElse(null); + + if (clientHello == null) { + throw new WorkflowExecutionException("No ClientHello received"); + } + + // Send HelloVerifyRequest for DTLS (as shown in the issue example) + executeAction(new SendAction(new HelloVerifyRequestMessage()), state); + + // Receive second ClientHello (after cookie verification) + receiveClientHello = new ReceiveAction(new ClientHelloMessage()); + executeAction(receiveClientHello, state); + + clientHello = (ClientHelloMessage) receiveClientHello.getReceivedMessages().stream() + .filter(msg -> msg instanceof ClientHelloMessage) + .findFirst() + .orElse(null); + + if (clientHello == null) { + throw new WorkflowExecutionException("No second ClientHello received"); + } + + // Check session ID to determine handshake type + byte[] receivedSessionId = clientHello.getSessionId().getValue(); + boolean isResumption = receivedSessionId != null && + receivedSessionId.length > 0 && + Arrays.equals(receivedSessionId, expectedSessionId); + + if (isResumption) { + LOGGER.info("Session resumption detected. Executing abbreviated handshake..."); + executeResumptionHandshake(state); + } else { + LOGGER.info("New session detected. Executing full handshake..."); + executeFullHandshake(state); + } + } + + /** + * Executes session resumption handshake + */ + private static void executeResumptionHandshake(State state) + throws WorkflowExecutionException, IOException { + + // Session resumption flow + executeAction(new SendAction(new ServerHelloMessage()), state); + executeAction(new SendAction(new ChangeCipherSpecMessage()), state); + executeAction(new SendAction(new FinishedMessage()), state); + executeAction(new ReceiveAction(new ChangeCipherSpecMessage()), state); + executeAction(new ReceiveAction(new FinishedMessage()), state); + + LOGGER.info("Session resumption handshake completed successfully"); + } + + /** + * Executes full handshake + */ + private static void executeFullHandshake(State state) + throws WorkflowExecutionException, IOException { + + // Full handshake flow + executeAction(new SendAction(new ServerHelloMessage()), state); + executeAction(new SendAction(new PskServerKeyExchangeMessage()), state); + executeAction(new SendAction(new ServerHelloDoneMessage()), state); + executeAction(new ReceiveAction(new PskClientKeyExchangeMessage()), state); + executeAction(new ReceiveAction(new ChangeCipherSpecMessage()), state); + executeAction(new ReceiveAction(new FinishedMessage()), state); + executeAction(new SendAction(new ChangeCipherSpecMessage()), state); + executeAction(new SendAction(new FinishedMessage()), state); + + LOGGER.info("Full handshake completed successfully"); + } + + /** + * Helper method to execute a single action + */ + private static void executeAction(TlsAction action, State state) + throws WorkflowExecutionException, IOException { + + action.setConnectionAlias(state.getTlsContext().getConnection().getAlias()); + action.normalize(); + action.execute(state); + + if (!action.executedAsPlanned()) { + LOGGER.warn("Action did not execute as planned: {}", action.getClass().getSimpleName()); + } + } + + /** + * Helper method to convert bytes to hex string + */ + private static String bytesToHex(byte[] bytes) { + if (bytes == null) return "null"; + StringBuilder result = new StringBuilder(); + for (byte b : bytes) { + result.append(String.format("%02x", b)); + } + return result.toString(); + } +} \ No newline at end of file diff --git a/TLS-Core/src/main/java/de/rub/nds/tlsattacker/core/examples/DynamicHandshakeWorkflowExample.java b/TLS-Core/src/main/java/de/rub/nds/tlsattacker/core/examples/DynamicHandshakeWorkflowExample.java new file mode 100644 index 000000000..0fae4ab21 --- /dev/null +++ b/TLS-Core/src/main/java/de/rub/nds/tlsattacker/core/examples/DynamicHandshakeWorkflowExample.java @@ -0,0 +1,237 @@ +/* + * TLS-Attacker - A Modular Penetration Testing Framework for TLS + * + * Copyright 2014-2025 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.examples; + +import de.rub.nds.tlsattacker.core.config.Config; +import de.rub.nds.tlsattacker.core.connection.InboundConnection; +import de.rub.nds.tlsattacker.core.constants.RunningModeType; +import de.rub.nds.tlsattacker.core.exceptions.WorkflowExecutionException; +import de.rub.nds.tlsattacker.core.layer.context.TlsContext; +import de.rub.nds.tlsattacker.core.protocol.message.*; +import de.rub.nds.tlsattacker.core.state.State; +import de.rub.nds.tlsattacker.core.workflow.DefaultWorkflowExecutor; +import de.rub.nds.tlsattacker.core.workflow.WorkflowTrace; +import de.rub.nds.tlsattacker.core.workflow.action.ReceiveAction; +import de.rub.nds.tlsattacker.core.workflow.action.ResetConnectionAction; +import de.rub.nds.tlsattacker.core.workflow.action.SendAction; +import de.rub.nds.tlsattacker.core.workflow.action.TlsAction; +import java.io.IOException; +import java.util.Arrays; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +/** + * Advanced example showing how to use WorkflowExecutor with conditional execution + * for handling both session resumption and full handshake scenarios. + * + * This example provides a more integrated approach using the WorkflowExecutor + * while still maintaining the ability to handle different handshake paths. + */ +public class DynamicHandshakeWorkflowExample { + + private static final Logger LOGGER = LogManager.getLogger(); + + public static void main(String[] args) { + // Create initial configuration + Config config = createServerConfig(); + + try { + // Execute initial full handshake + byte[] sessionId = executeInitialHandshake(config); + LOGGER.info("Initial handshake completed. Session ID: {}", bytesToHex(sessionId)); + + // Handle subsequent connections with dynamic workflow + handleDynamicConnection(config, sessionId); + + } catch (Exception e) { + LOGGER.error("Error during execution", e); + } + } + + /** + * Creates server configuration for PSK handshakes + */ + private static Config createServerConfig() { + Config config = Config.createConfig(); + config.setDefaultRunningMode(RunningModeType.SERVER); + + // Configure PSK + config.setDefaultSelectedCipherSuite("TLS_PSK_WITH_AES_128_CBC_SHA"); + config.setDefaultServerSupportedCipherSuites(Arrays.asList("TLS_PSK_WITH_AES_128_CBC_SHA")); + config.setDefaultPSKIdentity("Client_identity".getBytes()); + config.setDefaultPSKKey(new byte[]{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F}); + + // Configure connection + config.setDefaultConnections(Arrays.asList(new InboundConnection(4433, "server"))); + + return config; + } + + /** + * Executes initial full handshake using WorkflowExecutor + */ + private static byte[] executeInitialHandshake(Config config) throws WorkflowExecutionException { + // Create workflow trace for initial handshake + WorkflowTrace trace = new WorkflowTrace(); + + // Initial handshake flow + trace.addTlsAction(new ReceiveAction(new ClientHelloMessage())); + trace.addTlsAction(new SendAction(new HelloVerifyRequestMessage())); + trace.addTlsAction(new ReceiveAction(new ClientHelloMessage())); + trace.addTlsAction(new SendAction(new ServerHelloMessage())); + trace.addTlsAction(new SendAction(new PskServerKeyExchangeMessage())); + trace.addTlsAction(new SendAction(new ServerHelloDoneMessage())); + trace.addTlsAction(new ReceiveAction(new PskClientKeyExchangeMessage())); + trace.addTlsAction(new ReceiveAction(new ChangeCipherSpecMessage())); + trace.addTlsAction(new ReceiveAction(new FinishedMessage())); + trace.addTlsAction(new SendAction(new ChangeCipherSpecMessage())); + trace.addTlsAction(new SendAction(new FinishedMessage())); + trace.addTlsAction(new ReceiveAction(new AlertMessage())); + trace.addTlsAction(new ResetConnectionAction()); + + // Execute workflow + State state = new State(config, trace); + DefaultWorkflowExecutor executor = new DefaultWorkflowExecutor(state); + executor.executeWorkflow(); + + return state.getTlsContext().getServerSessionId(); + } + + /** + * Handles subsequent connection with dynamic workflow based on session ID + */ + private static void handleDynamicConnection(Config config, byte[] expectedSessionId) + throws WorkflowExecutionException, IOException { + + // Create partial workflow up to the decision point + WorkflowTrace initialTrace = new WorkflowTrace(); + initialTrace.addTlsAction(new ReceiveAction(new ClientHelloMessage())); + initialTrace.addTlsAction(new SendAction(new HelloVerifyRequestMessage())); + initialTrace.addTlsAction(new ReceiveAction(new ClientHelloMessage())); + + State state = new State(config, initialTrace); + + // Execute up to the point where we need to check session ID + executeActionsUntilDecisionPoint(state); + + // Determine handshake type based on received ClientHello + ClientHelloMessage clientHello = findLastClientHello(state); + boolean isResumption = checkSessionResumption(clientHello, expectedSessionId); + + // Create appropriate workflow continuation + WorkflowTrace continuationTrace = createContinuationWorkflow(isResumption); + + // Add continuation actions to state and execute + for (TlsAction action : continuationTrace.getTlsActions()) { + state.getWorkflowTrace().addTlsAction(action); + } + + // Continue execution + DefaultWorkflowExecutor executor = new DefaultWorkflowExecutor(state); + executor.executeWorkflow(); + + LOGGER.info("Dynamic handshake completed. Type: {}", + isResumption ? "Session Resumption" : "Full Handshake"); + } + + /** + * Executes actions up to the decision point + */ + private static void executeActionsUntilDecisionPoint(State state) + throws WorkflowExecutionException, IOException { + + for (TlsAction action : state.getWorkflowTrace().getTlsActions()) { + if (!action.isExecuted()) { + action.setConnectionAlias(state.getTlsContext().getConnection().getAlias()); + action.normalize(); + action.execute(state); + + if (!action.executedAsPlanned()) { + LOGGER.warn("Action did not execute as planned: {}", + action.getClass().getSimpleName()); + } + } + } + } + + /** + * Finds the last received ClientHello message + */ + private static ClientHelloMessage findLastClientHello(State state) { + for (int i = state.getWorkflowTrace().getTlsActions().size() - 1; i >= 0; i--) { + TlsAction action = state.getWorkflowTrace().getTlsActions().get(i); + if (action instanceof ReceiveAction) { + ReceiveAction receiveAction = (ReceiveAction) action; + for (ProtocolMessage msg : receiveAction.getReceivedMessages()) { + if (msg instanceof ClientHelloMessage) { + return (ClientHelloMessage) msg; + } + } + } + } + return null; + } + + /** + * Checks if the ClientHello indicates session resumption + */ + private static boolean checkSessionResumption(ClientHelloMessage clientHello, + byte[] expectedSessionId) { + if (clientHello == null || expectedSessionId == null) { + return false; + } + + byte[] receivedSessionId = clientHello.getSessionId().getValue(); + return receivedSessionId != null && + receivedSessionId.length > 0 && + Arrays.equals(receivedSessionId, expectedSessionId); + } + + /** + * Creates continuation workflow based on handshake type + */ + private static WorkflowTrace createContinuationWorkflow(boolean isResumption) { + WorkflowTrace trace = new WorkflowTrace(); + + if (isResumption) { + // Session resumption flow + trace.addTlsAction(new SendAction(new ServerHelloMessage())); + trace.addTlsAction(new SendAction(new ChangeCipherSpecMessage())); + trace.addTlsAction(new SendAction(new FinishedMessage())); + trace.addTlsAction(new ReceiveAction(new ChangeCipherSpecMessage())); + trace.addTlsAction(new ReceiveAction(new FinishedMessage())); + } else { + // Full handshake flow + trace.addTlsAction(new SendAction(new ServerHelloMessage())); + trace.addTlsAction(new SendAction(new PskServerKeyExchangeMessage())); + trace.addTlsAction(new SendAction(new ServerHelloDoneMessage())); + trace.addTlsAction(new ReceiveAction(new PskClientKeyExchangeMessage())); + trace.addTlsAction(new ReceiveAction(new ChangeCipherSpecMessage())); + trace.addTlsAction(new ReceiveAction(new FinishedMessage())); + trace.addTlsAction(new SendAction(new ChangeCipherSpecMessage())); + trace.addTlsAction(new SendAction(new FinishedMessage())); + } + + return trace; + } + + /** + * Helper method to convert bytes to hex string + */ + private static String bytesToHex(byte[] bytes) { + if (bytes == null) return "null"; + StringBuilder result = new StringBuilder(); + for (byte b : bytes) { + result.append(String.format("%02x", b)); + } + return result.toString(); + } +} \ No newline at end of file diff --git a/TLS-Core/src/main/java/de/rub/nds/tlsattacker/core/examples/README.md b/TLS-Core/src/main/java/de/rub/nds/tlsattacker/core/examples/README.md new file mode 100644 index 000000000..2077bffa4 --- /dev/null +++ b/TLS-Core/src/main/java/de/rub/nds/tlsattacker/core/examples/README.md @@ -0,0 +1,150 @@ +# Dynamic Handshake Handling in TLS-Attacker + +This directory contains examples demonstrating how to handle simultaneous full handshake and session resumption scenarios in TLS-Attacker, addressing issue #195. + +## Problem Statement + +When implementing a TLS server using TLS-Attacker, you may need to handle two different handshake scenarios for a second ClientHello: +1. **Session Resumption**: ClientHello includes the Session ID from a previous session +2. **New Full Handshake**: ClientHello contains an empty Session ID + +TLS-Attacker's workflow traces are typically static and don't support conditional branching, making it challenging to handle both cases in a single workflow. + +## Solution Approach + +The examples demonstrate two approaches to solve this problem: + +### 1. Manual Action Execution (`DynamicHandshakeExample.java`) + +This approach executes actions individually and makes decisions based on the received messages: + +```java +// Execute actions up to decision point +executeAction(new ReceiveAction(new ClientHelloMessage()), state); + +// Check received ClientHello +ClientHelloMessage clientHello = findReceivedClientHello(state); +byte[] receivedSessionId = clientHello.getSessionId().getValue(); + +// Decide which path to take +if (isSessionResumption(receivedSessionId, expectedSessionId)) { + executeResumptionHandshake(state); +} else { + executeFullHandshake(state); +} +``` + +**Advantages:** +- Full control over execution flow +- Easy to implement conditional logic +- Can inspect state between actions + +**Disadvantages:** +- More manual code required +- Less integrated with TLS-Attacker's workflow system + +### 2. Hybrid Workflow Approach (`DynamicHandshakeWorkflowExample.java`) + +This approach uses WorkflowExecutor but modifies the workflow dynamically: + +```java +// Execute partial workflow up to decision point +WorkflowTrace initialTrace = new WorkflowTrace(); +initialTrace.addTlsAction(new ReceiveAction(new ClientHelloMessage())); +// ... execute initial actions + +// Determine handshake type +boolean isResumption = checkSessionResumption(clientHello, expectedSessionId); + +// Create and append appropriate continuation workflow +WorkflowTrace continuationTrace = createContinuationWorkflow(isResumption); +state.getWorkflowTrace().addAll(continuationTrace.getTlsActions()); + +// Continue execution with WorkflowExecutor +executor.executeWorkflow(); +``` + +**Advantages:** +- Uses standard WorkflowExecutor +- Maintains workflow trace for debugging +- More integrated with TLS-Attacker patterns + +**Disadvantages:** +- Requires interrupting and resuming workflow execution +- More complex implementation + +## Usage Example + +To use these examples in your project: + +1. **As a library (recommended)**: Create a Maven project that depends on TLS-Attacker and adapt the example code: + +```java +public class MyTlsServer { + public void handleConnection() { + Config config = createConfig(); + State state = new State(config); + + // Execute initial handshake + byte[] sessionId = executeInitialHandshake(state); + + // Handle subsequent connections dynamically + while (true) { + ClientHelloMessage hello = receiveClientHello(state); + if (isResumption(hello, sessionId)) { + executeResumptionHandshake(state); + } else { + executeFullHandshake(state); + } + } + } +} +``` + +2. **For testing**: Use the test examples to verify your implementation handles both scenarios correctly. + +## Key Implementation Details + +### Session ID Checking +```java +boolean isResumption = receivedSessionId != null && + receivedSessionId.length > 0 && + Arrays.equals(receivedSessionId, expectedSessionId); +``` + +### DTLS Cookie Handling +The examples include HelloVerifyRequest handling for DTLS: +```java +executeAction(new SendAction(new HelloVerifyRequestMessage()), state); +executeAction(new ReceiveAction(new ClientHelloMessage()), state); // Second hello after cookie +``` + +### PSK Configuration +The examples use PSK cipher suites as shown in the original issue: +```java +config.setDefaultSelectedCipherSuite("TLS_PSK_WITH_AES_128_CBC_SHA"); +config.setDefaultPSKIdentity("Client_identity".getBytes()); +config.setDefaultPSKKey(pskKey); +``` + +## Testing + +Run the included test class to verify the implementation: + +```bash +mvn test -Dtest=DynamicHandshakeExampleTest +``` + +## Further Customization + +You can extend these examples to: +- Support multiple session IDs +- Implement session cache/storage +- Add timeout handling for session resumption +- Support other handshake variations (e.g., TLS 1.3 PSK modes) + +## References + +- Issue #195: Handling Simultaneous Full Handshake and Session Resumption +- TLS-Attacker Documentation: https://github.com/tls-attacker/TLS-Attacker +- TLS Session Resumption: RFC 5246 Section 7.3 \ No newline at end of file diff --git a/TLS-Core/src/test/java/de/rub/nds/tlsattacker/core/examples/DynamicHandshakeExampleTest.java b/TLS-Core/src/test/java/de/rub/nds/tlsattacker/core/examples/DynamicHandshakeExampleTest.java new file mode 100644 index 000000000..09ae7bb11 --- /dev/null +++ b/TLS-Core/src/test/java/de/rub/nds/tlsattacker/core/examples/DynamicHandshakeExampleTest.java @@ -0,0 +1,187 @@ +/* + * TLS-Attacker - A Modular Penetration Testing Framework for TLS + * + * Copyright 2014-2025 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.examples; + +import de.rub.nds.tlsattacker.core.config.Config; +import de.rub.nds.tlsattacker.core.connection.InboundConnection; +import de.rub.nds.tlsattacker.core.constants.HandshakeMessageType; +import de.rub.nds.tlsattacker.core.constants.ProtocolMessageType; +import de.rub.nds.tlsattacker.core.constants.RunningModeType; +import de.rub.nds.tlsattacker.core.layer.context.TlsContext; +import de.rub.nds.tlsattacker.core.protocol.message.*; +import de.rub.nds.tlsattacker.core.state.State; +import de.rub.nds.tlsattacker.core.workflow.WorkflowTrace; +import de.rub.nds.tlsattacker.core.workflow.action.ReceiveAction; +import de.rub.nds.tlsattacker.core.workflow.action.SendAction; +import de.rub.nds.tlsattacker.core.workflow.action.TlsAction; +import java.util.Arrays; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +/** + * Test class for DynamicHandshakeExample functionality + */ +public class DynamicHandshakeExampleTest { + + private Config config; + + @BeforeEach + public void setUp() { + config = Config.createConfig(); + config.setDefaultRunningMode(RunningModeType.SERVER); + config.setDefaultSelectedCipherSuite("TLS_PSK_WITH_AES_128_CBC_SHA"); + config.setDefaultServerSupportedCipherSuites(Arrays.asList("TLS_PSK_WITH_AES_128_CBC_SHA")); + config.setDefaultPSKIdentity("Client_identity".getBytes()); + config.setDefaultPSKKey(new byte[]{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F}); + config.setDefaultConnections(Arrays.asList(new InboundConnection(4433, "server"))); + } + + @Test + public void testSessionResumptionDetection() { + // Create test session ID + byte[] sessionId = new byte[]{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}; + + // Create ClientHello with session ID (resumption attempt) + ClientHelloMessage resumptionHello = new ClientHelloMessage(); + resumptionHello.setSessionId(sessionId); + + // Create ClientHello without session ID (new session) + ClientHelloMessage newSessionHello = new ClientHelloMessage(); + newSessionHello.setSessionId(new byte[0]); + + // Test resumption detection + assertTrue(isSessionResumption(resumptionHello, sessionId)); + assertFalse(isSessionResumption(newSessionHello, sessionId)); + assertFalse(isSessionResumption(null, sessionId)); + assertFalse(isSessionResumption(resumptionHello, null)); + } + + @Test + public void testWorkflowCreationForResumption() { + WorkflowTrace resumptionTrace = createResumptionWorkflow(); + + // Verify correct number of actions + assertEquals(5, resumptionTrace.getTlsActions().size()); + + // Verify action sequence + assertTrue(resumptionTrace.getTlsActions().get(0) instanceof SendAction); + assertTrue(((SendAction)resumptionTrace.getTlsActions().get(0)).getSendMessages().get(0) + instanceof ServerHelloMessage); + + assertTrue(resumptionTrace.getTlsActions().get(1) instanceof SendAction); + assertTrue(((SendAction)resumptionTrace.getTlsActions().get(1)).getSendMessages().get(0) + instanceof ChangeCipherSpecMessage); + + assertTrue(resumptionTrace.getTlsActions().get(2) instanceof SendAction); + assertTrue(((SendAction)resumptionTrace.getTlsActions().get(2)).getSendMessages().get(0) + instanceof FinishedMessage); + + assertTrue(resumptionTrace.getTlsActions().get(3) instanceof ReceiveAction); + assertTrue(resumptionTrace.getTlsActions().get(4) instanceof ReceiveAction); + } + + @Test + public void testWorkflowCreationForFullHandshake() { + WorkflowTrace fullTrace = createFullHandshakeWorkflow(); + + // Verify correct number of actions + assertEquals(8, fullTrace.getTlsActions().size()); + + // Verify action sequence includes PSK key exchange + boolean hasPskServerKeyExchange = false; + for (TlsAction action : fullTrace.getTlsActions()) { + if (action instanceof SendAction) { + SendAction sendAction = (SendAction) action; + if (sendAction.getSendMessages().stream() + .anyMatch(msg -> msg instanceof PskServerKeyExchangeMessage)) { + hasPskServerKeyExchange = true; + break; + } + } + } + assertTrue(hasPskServerKeyExchange); + } + + @Test + public void testDynamicWorkflowSelection() { + // Test session ID + byte[] sessionId = new byte[]{0x0A, 0x0B, 0x0C, 0x0D}; + + // Create state with context + State state = new State(config); + TlsContext context = state.getTlsContext(); + context.setServerSessionId(sessionId); + + // Simulate receiving ClientHello with matching session ID + ClientHelloMessage resumptionHello = new ClientHelloMessage(); + resumptionHello.setSessionId(sessionId); + + WorkflowTrace selectedTrace = selectWorkflowBasedOnClientHello(resumptionHello, sessionId); + + // Should select resumption workflow (5 actions) + assertEquals(5, selectedTrace.getTlsActions().size()); + + // Simulate receiving ClientHello with empty session ID + ClientHelloMessage newSessionHello = new ClientHelloMessage(); + newSessionHello.setSessionId(new byte[0]); + + selectedTrace = selectWorkflowBasedOnClientHello(newSessionHello, sessionId); + + // Should select full handshake workflow (8 actions) + assertEquals(8, selectedTrace.getTlsActions().size()); + } + + // Helper methods + + private boolean isSessionResumption(ClientHelloMessage clientHello, byte[] expectedSessionId) { + if (clientHello == null || expectedSessionId == null) { + return false; + } + + byte[] receivedSessionId = clientHello.getSessionId().getValue(); + return receivedSessionId != null && + receivedSessionId.length > 0 && + Arrays.equals(receivedSessionId, expectedSessionId); + } + + private WorkflowTrace createResumptionWorkflow() { + WorkflowTrace trace = new WorkflowTrace(); + trace.addTlsAction(new SendAction(new ServerHelloMessage())); + trace.addTlsAction(new SendAction(new ChangeCipherSpecMessage())); + trace.addTlsAction(new SendAction(new FinishedMessage())); + trace.addTlsAction(new ReceiveAction(new ChangeCipherSpecMessage())); + trace.addTlsAction(new ReceiveAction(new FinishedMessage())); + return trace; + } + + private WorkflowTrace createFullHandshakeWorkflow() { + WorkflowTrace trace = new WorkflowTrace(); + trace.addTlsAction(new SendAction(new ServerHelloMessage())); + trace.addTlsAction(new SendAction(new PskServerKeyExchangeMessage())); + trace.addTlsAction(new SendAction(new ServerHelloDoneMessage())); + trace.addTlsAction(new ReceiveAction(new PskClientKeyExchangeMessage())); + trace.addTlsAction(new ReceiveAction(new ChangeCipherSpecMessage())); + trace.addTlsAction(new ReceiveAction(new FinishedMessage())); + trace.addTlsAction(new SendAction(new ChangeCipherSpecMessage())); + trace.addTlsAction(new SendAction(new FinishedMessage())); + return trace; + } + + private WorkflowTrace selectWorkflowBasedOnClientHello(ClientHelloMessage clientHello, + byte[] expectedSessionId) { + if (isSessionResumption(clientHello, expectedSessionId)) { + return createResumptionWorkflow(); + } else { + return createFullHandshakeWorkflow(); + } + } +} \ No newline at end of file