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