Merge branch 'pt-tls'
[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
106 DBG1(DBG_TNC, "sending offer for PT-TLS version %d", PT_TLS_VERSION);
107
108 writer = bio_writer_create(4);
109 writer->write_uint8(writer, 0);
110 writer->write_uint8(writer, PT_TLS_VERSION);
111 writer->write_uint8(writer, PT_TLS_VERSION);
112 writer->write_uint8(writer, PT_TLS_VERSION);
113 if (!pt_tls_write(this->tls, writer, PT_TLS_VERSION_REQUEST,
114 this->identifier++))
115 {
116 return FALSE;
117 }
118
119 reader = pt_tls_read(this->tls, &vendor, &type, &identifier);
120 if (!reader)
121 {
122 return FALSE;
123 }
124 if (vendor != 0 || type != PT_TLS_VERSION_RESPONSE ||
125 !reader->read_uint24(reader, &reserved) ||
126 !reader->read_uint8(reader, &version) ||
127 version != PT_TLS_VERSION)
128 {
129 DBG1(DBG_TNC, "PT-TLS version negotiation failed");
130 reader->destroy(reader);
131 return FALSE;
132 }
133 reader->destroy(reader);
134 return TRUE;
135 }
136
137 /**
138 * Run a SASL mechanism
139 */
140 static status_t do_sasl(private_pt_tls_client_t *this, sasl_mechanism_t *sasl)
141 {
142 u_int32_t type, vendor, identifier;
143 u_int8_t result;
144 bio_reader_t *reader;
145 bio_writer_t *writer;
146 chunk_t data;
147
148 writer = bio_writer_create(32);
149 writer->write_data8(writer, chunk_from_str(sasl->get_name(sasl)));
150 switch (sasl->build(sasl, &data))
151 {
152 case INVALID_STATE:
153 break;
154 case NEED_MORE:
155 writer->write_data(writer, data);
156 free(data.ptr);
157 break;
158 case SUCCESS:
159 /* shouldn't happen */
160 free(data.ptr);
161 /* FALL */
162 case FAILED:
163 default:
164 writer->destroy(writer);
165 return FAILED;
166 }
167 if (!pt_tls_write(this->tls, writer, PT_TLS_SASL_MECH_SELECTION,
168 this->identifier++))
169 {
170 return FAILED;
171 }
172 while (TRUE)
173 {
174 reader = pt_tls_read(this->tls, &vendor, &type, &identifier);
175 if (!reader)
176 {
177 return FAILED;
178 }
179 if (vendor != 0)
180 {
181 reader->destroy(reader);
182 return FAILED;
183 }
184 switch (type)
185 {
186 case PT_TLS_SASL_AUTH_DATA:
187 switch (sasl->process(sasl, reader->peek(reader)))
188 {
189 case NEED_MORE:
190 reader->destroy(reader);
191 break;
192 case SUCCESS:
193 /* should not happen, as it would come in a RESULT */
194 case FAILED:
195 default:
196 reader->destroy(reader);
197 return FAILED;
198 }
199 break;
200 case PT_TLS_SASL_RESULT:
201 if (!reader->read_uint8(reader, &result))
202 {
203 reader->destroy(reader);
204 return FAILED;
205 }
206 switch (result)
207 {
208 case PT_TLS_SASL_RESULT_ABORT:
209 DBG1(DBG_TNC, "received SASL abort result");
210 reader->destroy(reader);
211 return FAILED;
212 case PT_TLS_SASL_RESULT_SUCCESS:
213 DBG1(DBG_TNC, "received SASL success result");
214 switch (sasl->process(sasl, reader->peek(reader)))
215 {
216 case SUCCESS:
217 reader->destroy(reader);
218 return SUCCESS;
219 case NEED_MORE:
220 /* inacceptable, it won't get more. FALL */
221 case FAILED:
222 default:
223 reader->destroy(reader);
224 return FAILED;
225 }
226 break;
227 case PT_TLS_SASL_RESULT_MECH_FAILURE:
228 case PT_TLS_SASL_RESULT_FAILURE:
229 DBG1(DBG_TNC, "received SASL failure result");
230 /* non-fatal failure, try again */
231 reader->destroy(reader);
232 return NEED_MORE;
233 }
234 default:
235 return FAILED;
236 }
237
238 writer = bio_writer_create(32);
239 switch (sasl->build(sasl, &data))
240 {
241 case INVALID_STATE:
242 break;
243 case SUCCESS:
244 /* shoudln't happen, continue until we get a result */
245 case NEED_MORE:
246 writer->write_data(writer, data);
247 free(data.ptr);
248 break;
249 case FAILED:
250 default:
251 writer->destroy(writer);
252 return FAILED;
253 }
254 if (!pt_tls_write(this->tls, writer, PT_TLS_SASL_AUTH_DATA,
255 this->identifier++))
256 {
257 return FAILED;
258 }
259 }
260 }
261
262 /**
263 * Read SASL mechanism list, select and run mechanism
264 */
265 static status_t select_and_do_sasl(private_pt_tls_client_t *this)
266 {
267 bio_reader_t *reader;
268 sasl_mechanism_t *sasl = NULL;
269 u_int32_t type, vendor, identifier;
270 u_int8_t len;
271 chunk_t chunk;
272 char buf[21];
273 status_t status = NEED_MORE;
274
275 reader = pt_tls_read(this->tls, &vendor, &type, &identifier);
276 if (!reader)
277 {
278 return FAILED;
279 }
280 if (vendor != 0 || type != PT_TLS_SASL_MECHS)
281 {
282 reader->destroy(reader);
283 return FAILED;
284 }
285 if (!reader->remaining(reader))
286 { /* mechanism list empty, SASL completed */
287 DBG1(DBG_TNC, "PT-TLS authentication complete");
288 reader->destroy(reader);
289 return SUCCESS;
290 }
291 while (reader->remaining(reader))
292 {
293 if (!reader->read_uint8(reader, &len) ||
294 !reader->read_data(reader, len & 0x1F, &chunk))
295 {
296 reader->destroy(reader);
297 return FAILED;
298 }
299 snprintf(buf, sizeof(buf), "%.*s", (int)chunk.len, chunk.ptr);
300 sasl = sasl_mechanism_create(buf, this->client);
301 if (sasl)
302 {
303 break;
304 }
305 }
306 reader->destroy(reader);
307
308 if (!sasl)
309 {
310 /* TODO: send PT-TLS error (5) */
311 return FAILED;
312 }
313 while (status == NEED_MORE)
314 {
315 status = do_sasl(this, sasl);
316 }
317 sasl->destroy(sasl);
318 if (status == SUCCESS)
319 { /* continue until we receive empty SASL mechanism list */
320 return NEED_MORE;
321 }
322 return FAILED;
323 }
324
325 /**
326 * Authenticate session using SASL
327 */
328 static bool authenticate(private_pt_tls_client_t *this)
329 {
330 while (TRUE)
331 {
332 switch (select_and_do_sasl(this))
333 {
334 case NEED_MORE:
335 continue;
336 case SUCCESS:
337 return TRUE;
338 case FAILED:
339 default:
340 return FALSE;
341 }
342 }
343 }
344
345 /**
346 * Perform assessment
347 */
348 static bool assess(private_pt_tls_client_t *this, tls_t *tnccs)
349 {
350 while (TRUE)
351 {
352 bio_writer_t *writer;
353 bio_reader_t *reader;
354 u_int32_t vendor, type, identifier;
355 chunk_t data;
356
357 writer = bio_writer_create(32);
358 while (TRUE)
359 {
360 char buf[2048];
361 size_t buflen, msglen;
362
363 buflen = sizeof(buf);
364 switch (tnccs->build(tnccs, buf, &buflen, &msglen))
365 {
366 case SUCCESS:
367 writer->destroy(writer);
368 return tnccs->is_complete(tnccs);
369 case FAILED:
370 default:
371 writer->destroy(writer);
372 return FALSE;
373 case INVALID_STATE:
374 writer->destroy(writer);
375 break;
376 case NEED_MORE:
377 writer->write_data(writer, chunk_create(buf, buflen));
378 continue;
379 case ALREADY_DONE:
380 writer->write_data(writer, chunk_create(buf, buflen));
381 if (!pt_tls_write(this->tls, writer, PT_TLS_PB_TNC_BATCH,
382 this->identifier++))
383 {
384 return FALSE;
385 }
386 writer = bio_writer_create(32);
387 continue;
388 }
389 break;
390 }
391
392 reader = pt_tls_read(this->tls, &vendor, &type, &identifier);
393 if (!reader)
394 {
395 return FALSE;
396 }
397 if (vendor == 0)
398 {
399 if (type == PT_TLS_ERROR)
400 {
401 DBG1(DBG_TNC, "received PT-TLS error");
402 reader->destroy(reader);
403 return FALSE;
404 }
405 if (type != PT_TLS_PB_TNC_BATCH)
406 {
407 DBG1(DBG_TNC, "unexpected PT-TLS message: %d", type);
408 reader->destroy(reader);
409 return FALSE;
410 }
411 data = reader->peek(reader);
412 switch (tnccs->process(tnccs, data.ptr, data.len))
413 {
414 case SUCCESS:
415 reader->destroy(reader);
416 return tnccs->is_complete(tnccs);
417 case FAILED:
418 default:
419 reader->destroy(reader);
420 return FALSE;
421 case NEED_MORE:
422 break;
423 }
424 }
425 else
426 {
427 DBG1(DBG_TNC, "ignoring vendor specific PT-TLS message");
428 }
429 reader->destroy(reader);
430 }
431 }
432
433 METHOD(pt_tls_client_t, run_assessment, status_t,
434 private_pt_tls_client_t *this, tnccs_t *tnccs)
435 {
436 if (!this->tls)
437 {
438 if (!make_connection(this))
439 {
440 return FAILED;
441 }
442 }
443 if (!negotiate_version(this))
444 {
445 return FAILED;
446 }
447 if (!authenticate(this))
448 {
449 return FAILED;
450 }
451 if (!assess(this, (tls_t*)tnccs))
452 {
453 return FAILED;
454 }
455 return SUCCESS;
456 }
457
458
459 METHOD(pt_tls_client_t, destroy, void,
460 private_pt_tls_client_t *this)
461 {
462 if (this->tls)
463 {
464 int fd;
465
466 fd = this->tls->get_fd(this->tls);
467 this->tls->destroy(this->tls);
468 close(fd);
469 }
470 this->address->destroy(this->address);
471 this->server->destroy(this->server);
472 this->client->destroy(this->client);
473 free(this);
474 }
475
476 /**
477 * See header
478 */
479 pt_tls_client_t *pt_tls_client_create(host_t *address, identification_t *server,
480 identification_t *client)
481 {
482 private_pt_tls_client_t *this;
483
484 INIT(this,
485 .public = {
486 .run_assessment = _run_assessment,
487 .destroy = _destroy,
488 },
489 .address = address,
490 .server = server,
491 .client = client,
492 );
493
494 return &this->public;
495 }