Skip to content

Commit e7b4ae3

Browse files
author
Developer
committed
Add examples for handling simultaneous full handshake and session resumption
This commit provides comprehensive examples demonstrating how to handle both session resumption and full handshake scenarios dynamically in TLS-Attacker. The solution addresses issue #195 by providing: - Manual action execution approach for full control - Hybrid workflow approach using WorkflowExecutor - Unit tests demonstrating the functionality - Comprehensive documentation explaining the solution These examples show how to inspect ClientHello messages at runtime and dynamically choose between resumption and full handshake workflows based on the session ID, solving the problem of static workflow traces.
1 parent b481012 commit e7b4ae3

File tree

4 files changed

+795
-0
lines changed

4 files changed

+795
-0
lines changed
Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
/*
2+
* TLS-Attacker - A Modular Penetration Testing Framework for TLS
3+
*
4+
* Copyright 2014-2025 Ruhr University Bochum, Paderborn University, Technology Innovation Institute,
5+
* and Hackmanit GmbH
6+
*
7+
* Licensed under Apache License, Version 2.0
8+
* http://www.apache.org/licenses/LICENSE-2.0.txt
9+
*/
10+
package de.rub.nds.tlsattacker.core.examples;
11+
12+
import de.rub.nds.tlsattacker.core.config.Config;
13+
import de.rub.nds.tlsattacker.core.connection.AliasedConnection;
14+
import de.rub.nds.tlsattacker.core.connection.InboundConnection;
15+
import de.rub.nds.tlsattacker.core.constants.ProtocolMessageType;
16+
import de.rub.nds.tlsattacker.core.constants.RunningModeType;
17+
import de.rub.nds.tlsattacker.core.exceptions.WorkflowExecutionException;
18+
import de.rub.nds.tlsattacker.core.layer.context.TlsContext;
19+
import de.rub.nds.tlsattacker.core.protocol.message.AlertMessage;
20+
import de.rub.nds.tlsattacker.core.protocol.message.ChangeCipherSpecMessage;
21+
import de.rub.nds.tlsattacker.core.protocol.message.ClientHelloMessage;
22+
import de.rub.nds.tlsattacker.core.protocol.message.FinishedMessage;
23+
import de.rub.nds.tlsattacker.core.protocol.message.HelloVerifyRequestMessage;
24+
import de.rub.nds.tlsattacker.core.protocol.message.PskClientKeyExchangeMessage;
25+
import de.rub.nds.tlsattacker.core.protocol.message.PskServerKeyExchangeMessage;
26+
import de.rub.nds.tlsattacker.core.protocol.message.ServerHelloDoneMessage;
27+
import de.rub.nds.tlsattacker.core.protocol.message.ServerHelloMessage;
28+
import de.rub.nds.tlsattacker.core.state.State;
29+
import de.rub.nds.tlsattacker.core.workflow.action.ReceiveAction;
30+
import de.rub.nds.tlsattacker.core.workflow.action.ResetConnectionAction;
31+
import de.rub.nds.tlsattacker.core.workflow.action.SendAction;
32+
import de.rub.nds.tlsattacker.core.workflow.action.TlsAction;
33+
import java.io.IOException;
34+
import java.util.Arrays;
35+
import org.apache.logging.log4j.LogManager;
36+
import org.apache.logging.log4j.Logger;
37+
38+
/**
39+
* Example demonstrating how to handle both session resumption and full handshake
40+
* dynamically based on the ClientHello session ID.
41+
*
42+
* This implementation addresses the issue described in #195 where TLS-Attacker needs
43+
* to support different handshake paths based on whether the client is attempting
44+
* session resumption or a new full handshake.
45+
*/
46+
public class DynamicHandshakeExample {
47+
48+
private static final Logger LOGGER = LogManager.getLogger();
49+
50+
public static void main(String[] args) {
51+
Config config = Config.createConfig();
52+
config.setDefaultRunningMode(RunningModeType.SERVER);
53+
54+
// Configure for PSK as shown in the issue
55+
config.setDefaultSelectedCipherSuite("TLS_PSK_WITH_AES_128_CBC_SHA");
56+
config.setDefaultServerSupportedCipherSuites(Arrays.asList("TLS_PSK_WITH_AES_128_CBC_SHA"));
57+
58+
// Add connection configuration
59+
config.setDefaultConnections(Arrays.asList(new InboundConnection(4433, "server")));
60+
61+
try {
62+
// Execute first handshake
63+
State state = executeInitialHandshake(config);
64+
65+
// Save session information
66+
byte[] sessionId = state.getTlsContext().getServerSessionId();
67+
LOGGER.info("Initial handshake completed. Session ID: {}", bytesToHex(sessionId));
68+
69+
// Reset for second handshake
70+
state = new State(config);
71+
72+
// Execute second handshake with dynamic path selection
73+
executeSecondHandshake(state, sessionId);
74+
75+
} catch (Exception e) {
76+
LOGGER.error("Error during handshake execution", e);
77+
}
78+
}
79+
80+
/**
81+
* Executes the initial full handshake
82+
*/
83+
private static State executeInitialHandshake(Config config) throws WorkflowExecutionException, IOException {
84+
State state = new State(config);
85+
TlsContext context = state.getTlsContext();
86+
87+
LOGGER.info("Starting initial full handshake...");
88+
89+
// Execute initial handshake actions
90+
executeAction(new ReceiveAction(new ClientHelloMessage()), state);
91+
executeAction(new SendAction(new HelloVerifyRequestMessage()), state);
92+
executeAction(new ReceiveAction(new ClientHelloMessage()), state);
93+
executeAction(new SendAction(new ServerHelloMessage()), state);
94+
executeAction(new SendAction(new PskServerKeyExchangeMessage()), state);
95+
executeAction(new SendAction(new ServerHelloDoneMessage()), state);
96+
executeAction(new ReceiveAction(new PskClientKeyExchangeMessage()), state);
97+
executeAction(new ReceiveAction(new ChangeCipherSpecMessage()), state);
98+
executeAction(new ReceiveAction(new FinishedMessage()), state);
99+
executeAction(new SendAction(new ChangeCipherSpecMessage()), state);
100+
executeAction(new SendAction(new FinishedMessage()), state);
101+
executeAction(new ReceiveAction(new AlertMessage()), state);
102+
executeAction(new ResetConnectionAction(), state);
103+
104+
return state;
105+
}
106+
107+
/**
108+
* Executes the second handshake with dynamic path selection based on session ID
109+
*/
110+
private static void executeSecondHandshake(State state, byte[] expectedSessionId)
111+
throws WorkflowExecutionException, IOException {
112+
113+
LOGGER.info("Starting second handshake...");
114+
115+
// Receive first ClientHello
116+
ReceiveAction receiveClientHello = new ReceiveAction(new ClientHelloMessage());
117+
executeAction(receiveClientHello, state);
118+
119+
// Check if ClientHello was received
120+
ClientHelloMessage clientHello = (ClientHelloMessage) receiveClientHello.getReceivedMessages().stream()
121+
.filter(msg -> msg instanceof ClientHelloMessage)
122+
.findFirst()
123+
.orElse(null);
124+
125+
if (clientHello == null) {
126+
throw new WorkflowExecutionException("No ClientHello received");
127+
}
128+
129+
// Send HelloVerifyRequest for DTLS (as shown in the issue example)
130+
executeAction(new SendAction(new HelloVerifyRequestMessage()), state);
131+
132+
// Receive second ClientHello (after cookie verification)
133+
receiveClientHello = new ReceiveAction(new ClientHelloMessage());
134+
executeAction(receiveClientHello, state);
135+
136+
clientHello = (ClientHelloMessage) receiveClientHello.getReceivedMessages().stream()
137+
.filter(msg -> msg instanceof ClientHelloMessage)
138+
.findFirst()
139+
.orElse(null);
140+
141+
if (clientHello == null) {
142+
throw new WorkflowExecutionException("No second ClientHello received");
143+
}
144+
145+
// Check session ID to determine handshake type
146+
byte[] receivedSessionId = clientHello.getSessionId().getValue();
147+
boolean isResumption = receivedSessionId != null &&
148+
receivedSessionId.length > 0 &&
149+
Arrays.equals(receivedSessionId, expectedSessionId);
150+
151+
if (isResumption) {
152+
LOGGER.info("Session resumption detected. Executing abbreviated handshake...");
153+
executeResumptionHandshake(state);
154+
} else {
155+
LOGGER.info("New session detected. Executing full handshake...");
156+
executeFullHandshake(state);
157+
}
158+
}
159+
160+
/**
161+
* Executes session resumption handshake
162+
*/
163+
private static void executeResumptionHandshake(State state)
164+
throws WorkflowExecutionException, IOException {
165+
166+
// Session resumption flow
167+
executeAction(new SendAction(new ServerHelloMessage()), state);
168+
executeAction(new SendAction(new ChangeCipherSpecMessage()), state);
169+
executeAction(new SendAction(new FinishedMessage()), state);
170+
executeAction(new ReceiveAction(new ChangeCipherSpecMessage()), state);
171+
executeAction(new ReceiveAction(new FinishedMessage()), state);
172+
173+
LOGGER.info("Session resumption handshake completed successfully");
174+
}
175+
176+
/**
177+
* Executes full handshake
178+
*/
179+
private static void executeFullHandshake(State state)
180+
throws WorkflowExecutionException, IOException {
181+
182+
// Full handshake flow
183+
executeAction(new SendAction(new ServerHelloMessage()), state);
184+
executeAction(new SendAction(new PskServerKeyExchangeMessage()), state);
185+
executeAction(new SendAction(new ServerHelloDoneMessage()), state);
186+
executeAction(new ReceiveAction(new PskClientKeyExchangeMessage()), state);
187+
executeAction(new ReceiveAction(new ChangeCipherSpecMessage()), state);
188+
executeAction(new ReceiveAction(new FinishedMessage()), state);
189+
executeAction(new SendAction(new ChangeCipherSpecMessage()), state);
190+
executeAction(new SendAction(new FinishedMessage()), state);
191+
192+
LOGGER.info("Full handshake completed successfully");
193+
}
194+
195+
/**
196+
* Helper method to execute a single action
197+
*/
198+
private static void executeAction(TlsAction action, State state)
199+
throws WorkflowExecutionException, IOException {
200+
201+
action.setConnectionAlias(state.getTlsContext().getConnection().getAlias());
202+
action.normalize();
203+
action.execute(state);
204+
205+
if (!action.executedAsPlanned()) {
206+
LOGGER.warn("Action did not execute as planned: {}", action.getClass().getSimpleName());
207+
}
208+
}
209+
210+
/**
211+
* Helper method to convert bytes to hex string
212+
*/
213+
private static String bytesToHex(byte[] bytes) {
214+
if (bytes == null) return "null";
215+
StringBuilder result = new StringBuilder();
216+
for (byte b : bytes) {
217+
result.append(String.format("%02x", b));
218+
}
219+
return result.toString();
220+
}
221+
}

0 commit comments

Comments
 (0)