Optimized PT-TLS data transfer
[strongswan.git] / src / libpttls / pt_tls_client.c
1 /*
2 * Copyright (C) 2012 Martin Willi
3 * Copyright (C) 2012 revosec AG
4 *
5 * This program is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License as published by the
7 * Free Software Foundation; either version 2 of the License, or (at your
8 * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
9 *
10 * This program is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
13 * for more details.
14 */
15
16 #include "pt_tls_client.h"
17 #include "pt_tls.h"
18
19 #include <sasl/sasl_mechanism.h>
20
21 #include <tls_socket.h>
22 #include <utils/debug.h>
23
24 #include <errno.h>
25 #include <stdio.h>
26 #include <unistd.h>
27
28 typedef struct private_pt_tls_client_t private_pt_tls_client_t;
29
30 /**
31 * Private data of an pt_tls_client_t object.
32 */
33 struct private_pt_tls_client_t {
34
35 /**
36 * Public pt_tls_client_t interface.
37 */
38 pt_tls_client_t public;
39
40 /**
41 * TLS secured socket used by PT-TLS
42 */
43 tls_socket_t *tls;
44
45 /**
46 * Server address/port
47 */
48 host_t *address;
49
50 /**
51 * Server identity
52 */
53 identification_t *server;
54
55 /**
56 * Client authentication identity
57 */
58 identification_t *client;
59
60 /**
61 * Current PT-TLS message identifier
62 */
63 u_int32_t identifier;
64 };
65
66 /**
67 * Establish TLS secured TCP connection to TNC server
68 */
69 static bool make_connection(private_pt_tls_client_t *this)
70 {
71 int fd;
72
73 fd = socket(this->address->get_family(this->address), SOCK_STREAM, 0);
74 if (fd == -1)
75 {
76 DBG1(DBG_TNC, "opening PT-TLS socket failed: %s", strerror(errno));
77 return FALSE;
78 }
79 if (connect(fd, this->address->get_sockaddr(this->address),
80 *this->address->get_sockaddr_len(this->address)) == -1)
81 {
82 DBG1(DBG_TNC, "connecting to PT-TLS server failed: %s", strerror(errno));
83 close(fd);
84 return FALSE;
85 }
86
87 this->tls = tls_socket_create(FALSE, this->server, this->client, fd, NULL);
88 if (!this->tls)
89 {
90 close(fd);
91 return FALSE;
92 }
93 return TRUE;
94 }
95
96 /**
97 * Negotiate PT-TLS version
98 */
99 static bool negotiate_version(private_pt_tls_client_t *this)
100 {
101 bio_writer_t *writer;
102 bio_reader_t *reader;
103 u_int32_t type, vendor, identifier, reserved;
104 u_int8_t version;
105 bool res;
106
107 DBG1(DBG_TNC, "sending offer for PT-TLS version %d", PT_TLS_VERSION);
108
109 writer = bio_writer_create(4);
110 writer->write_uint8(writer, 0);
111 writer->write_uint8(writer, PT_TLS_VERSION);
112 writer->write_uint8(writer, PT_TLS_VERSION);
113 writer->write_uint8(writer, PT_TLS_VERSION);
114 res = pt_tls_write(this->tls, PT_TLS_VERSION_REQUEST, this->identifier++,
115 writer->get_buf(writer));
116 writer->destroy(writer);
117 if (!res)
118 {
119 return FALSE;
120 }
121
122 reader = pt_tls_read(this->tls, &vendor, &type, &identifier);
123 if (!reader)
124 {
125 return FALSE;
126 }
127 if (vendor != 0 || type != PT_TLS_VERSION_RESPONSE ||
128 !reader->read_uint24(reader, &reserved) ||
129 !reader->read_uint8(reader, &version) ||
130 version != PT_TLS_VERSION)
131 {
132 DBG1(DBG_TNC, "PT-TLS version negotiation failed");
133 reader->destroy(reader);
134 return FALSE;
135 }
136 reader->destroy(reader);
137 return TRUE;
138 }
139
140 /**
141 * Run a SASL mechanism
142 */
143 static status_t do_sasl(private_pt_tls_client_t *this, sasl_mechanism_t *sasl)
144 {
145 u_int32_t type, vendor, identifier;
146 u_int8_t result;
147 bio_reader_t *reader;
148 bio_writer_t *writer;
149 chunk_t data;
150 bool res;
151
152 writer = bio_writer_create(32);
153 writer->write_data8(writer, chunk_from_str(sasl->get_name(sasl)));
154 switch (sasl->build(sasl, &data))
155 {
156 case INVALID_STATE:
157 break;
158 case NEED_MORE:
159 writer->write_data(writer, data);
160 free(data.ptr);
161 break;
162 case SUCCESS:
163 /* shouldn't happen */
164 free(data.ptr);
165 /* FALL */
166 case FAILED:
167 default:
168 writer->destroy(writer);
169 return FAILED;
170 }
171 res = pt_tls_write(this->tls, PT_TLS_SASL_MECH_SELECTION,
172 this->identifier++, writer->get_buf(writer));
173 writer->destroy(writer);
174 if (!res)
175 {
176 return FAILED;
177 }
178 while (TRUE)
179 {
180 reader = pt_tls_read(this->tls, &vendor, &type, &identifier);
181 if (!reader)
182 {
183 return FAILED;
184 }
185 if (vendor != 0)
186 {
187 reader->destroy(reader);
188 return FAILED;
189 }
190 switch (type)
191 {
192 case PT_TLS_SASL_AUTH_DATA:
193 switch (sasl->process(sasl, reader->peek(reader)))
194 {
195 case NEED_MORE:
196 reader->destroy(reader);
197 break;
198 case SUCCESS:
199 /* should not happen, as it would come in a RESULT */
200 case FAILED:
201 default:
202 reader->destroy(reader);
203 return FAILED;
204 }
205 break;
206 case PT_TLS_SASL_RESULT:
207 if (!reader->read_uint8(reader, &result))
208 {
209 reader->destroy(reader);
210 return FAILED;
211 }
212 DBG1(DBG_TNC, "received SASL %N result",
213 pt_tls_sasl_result_names, result);
214
215 switch (result)
216 {
217 case PT_TLS_SASL_RESULT_ABORT:
218 reader->destroy(reader);
219 return FAILED;
220 case PT_TLS_SASL_RESULT_SUCCESS:
221 switch (sasl->process(sasl, reader->peek(reader)))
222 {
223 case SUCCESS:
224 reader->destroy(reader);
225 return SUCCESS;
226 case NEED_MORE:
227 /* inacceptable, it won't get more. FALL */
228 case FAILED:
229 default:
230 reader->destroy(reader);
231 return FAILED;
232 }
233 break;
234 case PT_TLS_SASL_RESULT_MECH_FAILURE:
235 case PT_TLS_SASL_RESULT_FAILURE:
236 /* non-fatal failure, try again */
237 reader->destroy(reader);
238 return NEED_MORE;
239 }
240 /* fall-through */
241 default:
242 reader->destroy(reader);
243 return FAILED;
244 }
245
246 writer = bio_writer_create(32);
247 switch (sasl->build(sasl, &data))
248 {
249 case INVALID_STATE:
250 break;
251 case SUCCESS:
252 /* shoudln't happen, continue until we get a result */
253 case NEED_MORE:
254 writer->write_data(writer, data);
255 free(data.ptr);
256 break;
257 case FAILED:
258 default:
259 writer->destroy(writer);
260 return FAILED;
261 }
262 res = pt_tls_write(this->tls, PT_TLS_SASL_AUTH_DATA,
263 this->identifier++, writer->get_buf(writer));
264 writer->destroy(writer);
265 if (!res)
266 {
267 return FAILED;
268 }
269 }
270 }
271
272 /**
273 * Read SASL mechanism list, select and run mechanism
274 */
275 static status_t select_and_do_sasl(private_pt_tls_client_t *this)
276 {
277 bio_reader_t *reader;
278 sasl_mechanism_t *sasl = NULL;
279 u_int32_t type, vendor, identifier;
280 u_int8_t len;
281 chunk_t chunk;
282 char buf[21];
283 status_t status = NEED_MORE;
284
285 reader = pt_tls_read(this->tls, &vendor, &type, &identifier);
286 if (!reader)
287 {
288 return FAILED;
289 }
290 if (vendor != 0 || type != PT_TLS_SASL_MECHS)
291 {
292 reader->destroy(reader);
293 return FAILED;
294 }
295 if (!reader->remaining(reader))
296 { /* mechanism list empty, SASL completed */
297 DBG1(DBG_TNC, "PT-TLS authentication complete");
298 reader->destroy(reader);
299 return SUCCESS;
300 }
301 while (reader->remaining(reader))
302 {
303 if (!reader->read_uint8(reader, &len) ||
304 !reader->read_data(reader, len & 0x1F, &chunk))
305 {
306 reader->destroy(reader);
307 return FAILED;
308 }
309 snprintf(buf, sizeof(buf), "%.*s", (int)chunk.len, chunk.ptr);
310 sasl = sasl_mechanism_create(buf, this->client);
311 if (sasl)
312 {
313 break;
314 }
315 }
316 reader->destroy(reader);
317
318 if (!sasl)
319 {
320 /* TODO: send PT-TLS error (5) */
321 return FAILED;
322 }
323 while (status == NEED_MORE)
324 {
325 status = do_sasl(this, sasl);
326 }
327 sasl->destroy(sasl);
328 if (status == SUCCESS)
329 { /* continue until we receive empty SASL mechanism list */
330 return NEED_MORE;
331 }
332 return FAILED;
333 }
334
335 /**
336 * Authenticate session using SASL
337 */
338 static bool authenticate(private_pt_tls_client_t *this)
339 {
340 while (TRUE)
341 {
342 switch (select_and_do_sasl(this))
343 {
344 case NEED_MORE:
345 continue;
346 case SUCCESS:
347 return TRUE;
348 case FAILED:
349 default:
350 return FALSE;
351 }
352 }
353 }
354
355 /**
356 * Perform assessment
357 */
358 static bool assess(private_pt_tls_client_t *this, tls_t *tnccs)
359 {
360 while (TRUE)
361 {
362 size_t msglen;
363 size_t buflen = PT_TLS_MAX_MESSAGE_LEN;
364 char buf[buflen];
365 bio_reader_t *reader;
366 u_int32_t vendor, type, identifier;
367 chunk_t data;
368
369 switch (tnccs->build(tnccs, buf, &buflen, &msglen))
370 {
371 case SUCCESS:
372 return tnccs->is_complete(tnccs);
373 case ALREADY_DONE:
374 data = chunk_create(buf, buflen);
375 if (!pt_tls_write(this->tls, PT_TLS_PB_TNC_BATCH,
376 this->identifier++, data))
377 {
378 return FALSE;
379 }
380 break;
381 case INVALID_STATE:
382 break;
383 case FAILED:
384 default:
385 return FALSE;
386 }
387
388 reader = pt_tls_read(this->tls, &vendor, &type, &identifier);
389 if (!reader)
390 {
391 return FALSE;
392 }
393 if (vendor == 0)
394 {
395 if (type == PT_TLS_ERROR)
396 {
397 DBG1(DBG_TNC, "received PT-TLS error");
398 reader->destroy(reader);
399 return FALSE;
400 }
401 if (type != PT_TLS_PB_TNC_BATCH)
402 {
403 DBG1(DBG_TNC, "unexpected PT-TLS message: %d", type);
404 reader->destroy(reader);
405 return FALSE;
406 }
407 data = reader->peek(reader);
408 switch (tnccs->process(tnccs, data.ptr, data.len))
409 {
410 case SUCCESS:
411 reader->destroy(reader);
412 return tnccs->is_complete(tnccs);
413 case FAILED:
414 default:
415 reader->destroy(reader);
416 return FALSE;
417 case NEED_MORE:
418 break;
419 }
420 }
421 else
422 {
423 DBG1(DBG_TNC, "ignoring vendor specific PT-TLS message");
424 }
425 reader->destroy(reader);
426 }
427 }
428
429 METHOD(pt_tls_client_t, run_assessment, status_t,
430 private_pt_tls_client_t *this, tnccs_t *tnccs)
431 {
432 if (!this->tls)
433 {
434 DBG1(DBG_TNC, "entering PT-TLS setup phase");
435 if (!make_connection(this))
436 {
437 return FAILED;
438 }
439 }
440
441 DBG1(DBG_TNC, "entering PT-TLS negotiation phase");
442 if (!negotiate_version(this))
443 {
444 return FAILED;
445 }
446
447 DBG1(DBG_TNC, "doing SASL client authentication");
448 if (!authenticate(this))
449 {
450 return FAILED;
451 }
452
453 DBG1(DBG_TNC, "entering PT-TLS data transport phase");
454 if (!assess(this, (tls_t*)tnccs))
455 {
456 return FAILED;
457 }
458 return SUCCESS;
459 }
460
461
462 METHOD(pt_tls_client_t, destroy, void,
463 private_pt_tls_client_t *this)
464 {
465 if (this->tls)
466 {
467 int fd;
468
469 fd = this->tls->get_fd(this->tls);
470 this->tls->destroy(this->tls);
471 close(fd);
472 }
473 this->address->destroy(this->address);
474 this->server->destroy(this->server);
475 this->client->destroy(this->client);
476 free(this);
477 }
478
479 /**
480 * See header
481 */
482 pt_tls_client_t *pt_tls_client_create(host_t *address, identification_t *server,
483 identification_t *client)
484 {
485 private_pt_tls_client_t *this;
486
487 INIT(this,
488 .public = {
489 .run_assessment = _run_assessment,
490 .destroy = _destroy,
491 },
492 .address = address,
493 .server = server,
494 .client = client,
495 );
496
497 return &this->public;
498 }