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