1use crate::llm::actions::{
4 client_trait::{Client, ClientActionResult},
5 protocol_trait::Protocol,
6 ActionDefinition, Parameter, ParameterDefinition,
7};
8use crate::protocol::EventType;
9use crate::state::app_state::AppState;
10use anyhow::{Context, Result};
11use serde_json::json;
12use std::sync::LazyLock;
13
14pub static TLS_CLIENT_CONNECTED_EVENT: LazyLock<EventType> = LazyLock::new(|| {
16 EventType::new(
17 "tls_client_connected",
18 "TLS client successfully completed handshake with server",
19 json!({
20 "type": "send_tls_data",
21 "data": "Hello"
22 }),
23 )
24 .with_parameters(vec![
25 Parameter {
26 name: "remote_addr".to_string(),
27 type_hint: "string".to_string(),
28 description: "Remote TLS server address".to_string(),
29 required: true,
30 },
31 Parameter {
32 name: "server_name".to_string(),
33 type_hint: "string".to_string(),
34 description: "SNI server name used for TLS handshake".to_string(),
35 required: true,
36 },
37 ])
38});
39
40pub static TLS_CLIENT_DATA_RECEIVED_EVENT: LazyLock<EventType> = LazyLock::new(|| {
42 EventType::new(
43 "tls_client_data_received",
44 "Data received from TLS server (decrypted)",
45 json!({
46 "type": "send_tls_data",
47 "data": "Response"
48 }),
49 )
50 .with_parameters(vec![
51 Parameter {
52 name: "data".to_string(),
53 type_hint: "string".to_string(),
54 description: "The decrypted data received (UTF-8 text or hex for binary)".to_string(),
55 required: true,
56 },
57 Parameter {
58 name: "data_length".to_string(),
59 type_hint: "number".to_string(),
60 description: "Length of data in bytes".to_string(),
61 required: true,
62 },
63 ])
64});
65
66pub struct TlsClientProtocol;
68
69impl Default for TlsClientProtocol {
70 fn default() -> Self {
71 Self::new()
72 }
73}
74
75impl TlsClientProtocol {
76 pub fn new() -> Self {
77 Self
78 }
79}
80
81impl Protocol for TlsClientProtocol {
83 fn get_startup_parameters(&self) -> Vec<ParameterDefinition> {
84 vec![
85 ParameterDefinition {
86 name: "accept_invalid_certs".to_string(),
87 type_hint: "boolean".to_string(),
88 description:
89 "Accept self-signed or invalid certificates (useful for testing, default: false)"
90 .to_string(),
91 required: false,
92 example: json!(false),
93 default: None,
94 },
95 ParameterDefinition {
96 name: "server_name".to_string(),
97 type_hint: "string".to_string(),
98 description: "SNI server name for TLS handshake (default: derived from remote_addr hostname)"
99 .to_string(),
100 required: false,
101 example: json!("example.com"),
102 default: None,
103 },
104 ParameterDefinition {
105 name: "custom_ca_cert_pem".to_string(),
106 type_hint: "string".to_string(),
107 description: "Custom CA certificate in PEM format to use for validation (instead of system roots)"
108 .to_string(),
109 required: false,
110 example: json!("-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----"),
111 default: None,
112 },
113 ]
114 }
115
116 fn get_async_actions(&self, _state: &AppState) -> Vec<ActionDefinition> {
117 vec![
118 ActionDefinition {
119 name: "send_tls_data".to_string(),
120 description: "Send data over TLS connection (UTF-8 string or hex-encoded)"
121 .to_string(),
122 parameters: vec![
123 Parameter {
124 name: "data".to_string(),
125 type_hint: "string".to_string(),
126 description: "UTF-8 string data to send (preferred for text)".to_string(),
127 required: false,
128 },
129 Parameter {
130 name: "data_hex".to_string(),
131 type_hint: "string".to_string(),
132 description: "Hexadecimal encoded data to send (for binary data)"
133 .to_string(),
134 required: false,
135 },
136 ],
137 example: json!({
138 "type": "send_tls_data",
139 "data": "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n"
140 }),
141 log_template: None,
142 },
143 ActionDefinition {
144 name: "disconnect".to_string(),
145 description: "Disconnect from the TLS server".to_string(),
146 parameters: vec![],
147 example: json!({
148 "type": "disconnect"
149 }),
150 log_template: None,
151 },
152 ]
153 }
154
155 fn get_sync_actions(&self) -> Vec<ActionDefinition> {
156 vec![
157 ActionDefinition {
158 name: "send_tls_data".to_string(),
159 description:
160 "Send TLS data in response to received data (UTF-8 string or hex-encoded)"
161 .to_string(),
162 parameters: vec![
163 Parameter {
164 name: "data".to_string(),
165 type_hint: "string".to_string(),
166 description: "UTF-8 string data to send (preferred for text)".to_string(),
167 required: false,
168 },
169 Parameter {
170 name: "data_hex".to_string(),
171 type_hint: "string".to_string(),
172 description: "Hexadecimal encoded data to send (for binary data)"
173 .to_string(),
174 required: false,
175 },
176 ],
177 example: json!({
178 "type": "send_tls_data",
179 "data": "Response data"
180 }),
181 log_template: None,
182 },
183 ActionDefinition {
184 name: "wait_for_more".to_string(),
185 description: "Wait for more data before responding".to_string(),
186 parameters: vec![],
187 example: json!({
188 "type": "wait_for_more"
189 }),
190 log_template: None,
191 },
192 ]
193 }
194
195 fn protocol_name(&self) -> &'static str {
196 "TLS"
197 }
198
199 fn get_event_types(&self) -> Vec<EventType> {
200 vec![
201 EventType::new(
202 "tls_client_connected",
203 "Triggered when TLS client completes handshake with server",
204 json!({"type": "placeholder", "event_id": "tls_client_connected"}),
205 ),
206 EventType::new(
207 "tls_client_data_received",
208 "Triggered when TLS client receives decrypted data from server",
209 json!({"type": "placeholder", "event_id": "tls_client_data_received"}),
210 ),
211 ]
212 }
213
214 fn stack_name(&self) -> &'static str {
215 "ETH>IP>TCP>TLS"
216 }
217
218 fn keywords(&self) -> Vec<&'static str> {
219 vec!["tls", "ssl", "tls client", "connect to tls", "https"]
220 }
221
222 fn metadata(&self) -> crate::protocol::metadata::ProtocolMetadataV2 {
223 use crate::protocol::metadata::{DevelopmentState, ProtocolMetadataV2};
224
225 ProtocolMetadataV2::builder()
226 .state(DevelopmentState::Experimental)
227 .implementation("tokio-rustls TLS client with certificate validation control")
228 .llm_control("Full control over encrypted data exchange")
229 .e2e_testing("NetGet TLS server for testing")
230 .notes("Generic TLS client for encrypted protocols - LLM implements application layer")
231 .build()
232 }
233
234 fn description(&self) -> &'static str {
235 "TLS client for connecting to TLS/SSL servers"
236 }
237
238 fn example_prompt(&self) -> &'static str {
239 "Connect to TLS at localhost:8443 and send an HTTP request"
240 }
241
242 fn group_name(&self) -> &'static str {
243 "Core"
244 }
245
246 fn get_startup_examples(&self) -> crate::llm::actions::StartupExamples {
247 use crate::llm::actions::StartupExamples;
248 use serde_json::json;
249
250 StartupExamples::new(
251 json!({
253 "type": "open_client",
254 "remote_addr": "example.com:443",
255 "base_stack": "tls",
256 "instruction": "Send an HTTPS GET request and display the response"
257 }),
258 json!({
260 "type": "open_client",
261 "remote_addr": "example.com:443",
262 "base_stack": "tls",
263 "event_handlers": [{
264 "event_pattern": "tls_client_data_received",
265 "handler": {
266 "type": "script",
267 "language": "python",
268 "code": "<tls_handler>"
269 }
270 }]
271 }),
272 json!({
274 "type": "open_client",
275 "remote_addr": "example.com:443",
276 "base_stack": "tls",
277 "event_handlers": [{
278 "event_pattern": "tls_client_connected",
279 "handler": {
280 "type": "static",
281 "actions": [{
282 "type": "send_tls_data",
283 "data": "GET / HTTP/1.1\r\nHost: example.com\r\nConnection: close\r\n\r\n"
284 }]
285 }
286 }]
287 }),
288 )
289 }
290}
291
292impl Client for TlsClientProtocol {
294 fn connect(
295 &self,
296 ctx: crate::protocol::ConnectContext,
297 ) -> std::pin::Pin<
298 Box<dyn std::future::Future<Output = anyhow::Result<std::net::SocketAddr>> + Send>,
299 > {
300 Box::pin(async move {
301 use crate::client::tls::TlsClient;
302 TlsClient::connect_with_llm_actions(
303 ctx.remote_addr,
304 ctx.llm_client,
305 ctx.state,
306 ctx.status_tx,
307 ctx.client_id,
308 ctx.startup_params,
309 )
310 .await
311 })
312 }
313
314 fn execute_action(&self, action: serde_json::Value) -> Result<ClientActionResult> {
315 let action_type = action
316 .get("type")
317 .and_then(|v| v.as_str())
318 .context("Missing 'type' field in action")?;
319
320 match action_type {
321 "send_tls_data" => {
322 let data = if let Some(utf8_data) = action.get("data").and_then(|v| v.as_str()) {
325 utf8_data.as_bytes().to_vec()
327 } else if let Some(hex_data) = action.get("data_hex").and_then(|v| v.as_str()) {
328 hex::decode(hex_data).context("Invalid hex data in data_hex field")?
330 } else {
331 return Err(anyhow::anyhow!(
332 "Missing 'data' or 'data_hex' field in send_tls_data action"
333 ));
334 };
335
336 Ok(ClientActionResult::SendData(data))
337 }
338 "disconnect" => Ok(ClientActionResult::Disconnect),
339 "wait_for_more" => Ok(ClientActionResult::WaitForMore),
340 _ => Err(anyhow::anyhow!(
341 "Unknown TLS client action: {}",
342 action_type
343 )),
344 }
345 }
346}