log final TLS acknowledgement packet
[strongswan.git] / src / libcharon / plugins / eap_ttls / eap_ttls.c
1 /*
2 * Copyright (C) 2010 Martin Willi, revosec AG
3 * Copyright (C) 2010 Andreas Steffen, HSR Hochschule fuer Technik Rapperswil
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 "eap_ttls.h"
17
18 #include <tls.h>
19
20 #include <daemon.h>
21 #include <library.h>
22
23 typedef struct private_eap_ttls_t private_eap_ttls_t;
24
25 /**
26 * Private data of an eap_ttls_t object.
27 */
28 struct private_eap_ttls_t {
29
30 /**
31 * Public interface.
32 */
33 eap_ttls_t public;
34
35 /**
36 * Number of EAP-TLS messages processed so far
37 */
38 int processed;
39
40 /**
41 * Is this method instance acting as server?
42 */
43 bool is_server;
44
45 /**
46 * TLS layers
47 */
48 tls_t *tls;
49
50 /**
51 * Allocated input buffer
52 */
53 chunk_t input;
54
55 /**
56 * Number of bytes read in input buffer
57 */
58 size_t inpos;
59
60 /**
61 * Allocated ouput buffer
62 */
63 chunk_t output;
64
65 /**
66 * Number of bytes sent from output buffer
67 */
68 size_t outpos;
69 };
70
71 /** Size limit for a single TLS message */
72 #define MAX_TLS_MESSAGE_LEN 16384
73 /** Size of a EAP-TLS fragment */
74 #define EAP_TTLS_FRAGMENT_LEN 1014
75 /** Maximum number of EAP-TLS messages/fragments allowed */
76 #define MAX_EAP_TTLS_MESSAGE_COUNT 16
77
78 /**
79 * Flags of an EAP-TLS message
80 */
81 typedef enum {
82 EAP_TTLS_LENGTH = (1<<7),
83 EAP_TTLS_MORE_FRAGS = (1<<6),
84 EAP_TTLS_START = (1<<5),
85 EAP_TTLS_VERSION = 0x07
86 } eap_ttls_flags_t;
87
88 /**
89 * EAP-TLS packet format
90 */
91 typedef struct __attribute__((packed)) {
92 u_int8_t code;
93 u_int8_t identifier;
94 u_int16_t length;
95 u_int8_t type;
96 u_int8_t flags;
97 } eap_ttls_packet_t;
98
99 /**
100 * TLS record
101 */
102 typedef struct __attribute__((packed)) {
103 u_int8_t type;
104 u_int16_t version;
105 u_int16_t length;
106 char data[];
107 } tls_record_t;
108
109 METHOD(eap_method_t, initiate, status_t,
110 private_eap_ttls_t *this, eap_payload_t **out)
111 {
112 if (this->is_server)
113 {
114 eap_ttls_packet_t pkt = {
115 .type = EAP_TTLS,
116 .code = EAP_REQUEST,
117 .flags = EAP_TTLS_START,
118 };
119 htoun16(&pkt.length, sizeof(eap_ttls_packet_t));
120 /* start with non-zero random identifier */
121 do {
122 pkt.identifier = random();
123 } while (!pkt.identifier);
124 DBG2(DBG_IKE, "sending TLS start packet");
125
126 *out = eap_payload_create_data(chunk_from_thing(pkt));
127 return NEED_MORE;
128 }
129 return FAILED;
130 }
131
132 /**
133 * Write received TLS data to the input buffer
134 */
135 static bool write_buf(private_eap_ttls_t *this, eap_ttls_packet_t *pkt)
136 {
137 u_int32_t msg_len;
138 u_int16_t pkt_len;
139 chunk_t data;
140
141 pkt_len = untoh16(&pkt->length);
142
143 if (pkt->flags & EAP_TTLS_LENGTH)
144 {
145 if (pkt_len < sizeof(eap_ttls_packet_t) + sizeof(msg_len))
146 {
147 DBG1(DBG_IKE, "EAP-TLS packet too short");
148 return FALSE;
149 }
150 msg_len = untoh32(pkt + 1);
151 if (msg_len < pkt_len - sizeof(eap_ttls_packet_t) - sizeof(msg_len) ||
152 msg_len > MAX_TLS_MESSAGE_LEN)
153 {
154 DBG1(DBG_IKE, "invalid EAP-TLS packet length");
155 return FALSE;
156 }
157 if (this->input.ptr)
158 {
159 if (msg_len != this->input.len)
160 {
161 DBG1(DBG_IKE, "received unexpected TLS message length");
162 return FALSE;
163 }
164 }
165 else
166 {
167 this->input = chunk_alloc(msg_len);
168 this->inpos = 0;
169 }
170 data = chunk_create((char*)(pkt + 1) + sizeof(msg_len),
171 pkt_len - sizeof(eap_ttls_packet_t) - sizeof(msg_len));
172 }
173 else
174 {
175 data = chunk_create((char*)(pkt + 1),
176 pkt_len - sizeof(eap_ttls_packet_t));
177 }
178 if (data.len > this->input.len - this->inpos)
179 {
180 DBG1(DBG_IKE, "EAP-TLS fragment exceeds TLS message length");
181 return FALSE;
182 }
183 memcpy(this->input.ptr + this->inpos, data.ptr, data.len);
184 this->inpos += data.len;
185 return TRUE;
186 }
187
188 /**
189 * Send an ack to request next fragment
190 */
191 static eap_payload_t *create_ack(private_eap_ttls_t *this, u_int8_t identifier)
192 {
193 eap_ttls_packet_t pkt = {
194 .code = this->is_server ? EAP_REQUEST : EAP_RESPONSE,
195 .identifier = this->is_server ? identifier + 1 : identifier,
196 .type = EAP_TTLS,
197 };
198 htoun16(&pkt.length, sizeof(pkt));
199 DBG2(DBG_IKE, "sending TLS acknowledgement packet");
200
201 return eap_payload_create_data(chunk_from_thing(pkt));
202 }
203
204 /**
205 * Create a eap response from data in the TLS output buffer
206 */
207 static eap_payload_t *read_buf(private_eap_ttls_t *this, u_int8_t identifier)
208 {
209 char buf[EAP_TTLS_FRAGMENT_LEN + sizeof(eap_ttls_packet_t) + 4], *start;
210 eap_ttls_packet_t *pkt = (eap_ttls_packet_t*)buf;
211 u_int16_t pkt_len = sizeof(eap_ttls_packet_t);
212
213 pkt->code = this->is_server ? EAP_REQUEST : EAP_RESPONSE;
214 pkt->identifier = this->is_server ? identifier + 1 : identifier;
215 pkt->type = EAP_TTLS;
216 pkt->flags = 0;
217
218 if (this->output.len)
219 {
220 start = (char*)(pkt + 1);
221 if (this->outpos == 0)
222 { /* first fragment */
223 pkt->flags = EAP_TTLS_LENGTH;
224 pkt_len += 4;
225 start += 4;
226 htoun32(pkt + 1, this->output.len);
227 }
228
229 if (this->output.len - this->outpos > EAP_TTLS_FRAGMENT_LEN)
230 {
231 pkt->flags |= EAP_TTLS_MORE_FRAGS;
232 pkt_len += EAP_TTLS_FRAGMENT_LEN;
233 memcpy(start, this->output.ptr + this->outpos, EAP_TTLS_FRAGMENT_LEN);
234 this->outpos += EAP_TTLS_FRAGMENT_LEN;
235 DBG2(DBG_IKE, "sending TLS packet fragment");
236 }
237 else
238 {
239 pkt_len += this->output.len - this->outpos;
240 memcpy(start, this->output.ptr + this->outpos,
241 this->output.len - this->outpos);
242 chunk_free(&this->output);
243 this->outpos = 0;
244 DBG2(DBG_IKE, "sending TLS packet");
245 }
246 }
247 else
248 {
249 DBG2(DBG_IKE, "sending TLS acknowledgement packet");
250 }
251 htoun16(&pkt->length, pkt_len);
252 return eap_payload_create_data(chunk_create(buf, pkt_len));
253 }
254
255 /**
256 * Pass data in input buffer to upper layers, write result to output buffer
257 */
258 static status_t process_buf(private_eap_ttls_t *this)
259 {
260 tls_record_t *in, out;
261 chunk_t data;
262 u_int16_t len;
263 status_t status;
264
265 /* pass input buffer to upper layer, record for record */
266 data = this->input;
267 while (data.len > sizeof(tls_record_t))
268 {
269 in = (tls_record_t*)data.ptr;
270 len = untoh16(&in->length);
271 if (len > data.len - sizeof(tls_record_t))
272 {
273 DBG1(DBG_IKE, "TLS record length invalid");
274 return FAILED;
275 }
276 if (untoh16(&in->version) < TLS_1_0)
277 {
278 DBG1(DBG_IKE, "%N invalid with EAP-TLS",
279 tls_version_names, untoh16(&in->version));
280 return FAILED;
281 }
282
283 status = this->tls->process(this->tls, in->type,
284 chunk_create(in->data, len));
285 if (status != NEED_MORE)
286 {
287 return status;
288 }
289 data = chunk_skip(data, len + sizeof(tls_record_t));
290 }
291 chunk_free(&this->input);
292 this->inpos = 0;
293
294 /* read in records from upper layer, append to output buffer */
295 chunk_free(&this->output);
296 while (TRUE)
297 {
298 tls_content_type_t type;
299 chunk_t header = chunk_from_thing(out);
300
301 status = this->tls->build(this->tls, &type, &data);
302 switch (status)
303 {
304 case NEED_MORE:
305 break;
306 case INVALID_STATE:
307 /* invalid state means we need more input from peer first */
308 return NEED_MORE;
309 case SUCCESS:
310 return SUCCESS;
311 case FAILED:
312 default:
313 return FAILED;
314 }
315 out.type = type;
316 htoun16(&out.version, this->tls->get_version(this->tls));
317 htoun16(&out.length, data.len);
318 this->output = chunk_cat("mcm", this->output, header, data);
319 }
320 }
321
322 METHOD(eap_method_t, process, status_t,
323 private_eap_ttls_t *this, eap_payload_t *in, eap_payload_t **out)
324 {
325 eap_ttls_packet_t *pkt;
326 chunk_t data;
327 status_t status;
328
329 if (++this->processed > MAX_EAP_TTLS_MESSAGE_COUNT)
330 {
331 DBG1(DBG_IKE, "EAP-TTLS packet count exceeded");
332 return FAILED;
333 }
334
335 data = in->get_data(in);
336
337 pkt = (eap_ttls_packet_t*)data.ptr;
338 if (data.len < sizeof(eap_ttls_packet_t) ||
339 untoh16(&pkt->length) != data.len)
340 {
341 DBG1(DBG_IKE, "invalid EAP-TTLS packet length");
342 return FAILED;
343 }
344 if (pkt->flags & EAP_TTLS_START)
345 {
346 DBG1(DBG_IKE, "EAP-TTLS version is v%u",
347 pkt->flags & EAP_TTLS_VERSION);
348 }
349 else
350 {
351 if (data.len == sizeof(eap_ttls_packet_t))
352 {
353 if (this->output.len)
354 { /* ACK to our fragment, send next */
355 *out = read_buf(this, pkt->identifier);
356 return NEED_MORE;
357 }
358 if (this->tls->is_complete(this->tls))
359 {
360 return SUCCESS;
361 }
362 return FAILED;
363 }
364 if (!write_buf(this, pkt))
365 {
366 return FAILED;
367 }
368 if (pkt->flags & EAP_TTLS_MORE_FRAGS)
369 { /* more fragments follow */
370 *out = create_ack(this, pkt->identifier);
371 return NEED_MORE;
372 }
373 else if (this->input.len != this->inpos)
374 {
375 DBG1(DBG_IKE, "defragmented TLS message has invalid length");
376 return FAILED;
377 }
378 }
379 status = process_buf(this);
380 if (status == NEED_MORE)
381 {
382 *out = read_buf(this, pkt->identifier);
383 }
384 return status;
385 }
386
387 METHOD(eap_method_t, get_type, eap_type_t,
388 private_eap_ttls_t *this, u_int32_t *vendor)
389 {
390 *vendor = 0;
391 return EAP_TTLS;
392 }
393
394 METHOD(eap_method_t, get_msk, status_t,
395 private_eap_ttls_t *this, chunk_t *msk)
396 {
397 *msk = this->tls->get_eap_msk(this->tls);
398 if (msk->len)
399 {
400 return SUCCESS;
401 }
402 return FAILED;
403 }
404
405 METHOD(eap_method_t, is_mutual, bool,
406 private_eap_ttls_t *this)
407 {
408 return TRUE;
409 }
410
411 METHOD(eap_method_t, destroy, void,
412 private_eap_ttls_t *this)
413 {
414 free(this->input.ptr);
415 free(this->output.ptr);
416
417 this->tls->destroy(this->tls);
418
419 free(this);
420 }
421
422 /**
423 * Generic private constructor
424 */
425 static eap_ttls_t *eap_ttls_create(identification_t *server,
426 identification_t *peer, bool is_server)
427 {
428 private_eap_ttls_t *this;
429
430 INIT(this,
431 .public.eap_method = {
432 .initiate = _initiate,
433 .process = _process,
434 .get_type = _get_type,
435 .is_mutual = _is_mutual,
436 .get_msk = _get_msk,
437 .destroy = _destroy,
438 },
439 .is_server = is_server,
440 );
441 /* MSK PRF ASCII constant label according to EAP-TTLS RFC 5281 */
442 this->tls = tls_create(is_server, server, peer, "ttls keying material");
443
444 return &this->public;
445 }
446
447 eap_ttls_t *eap_ttls_create_server(identification_t *server,
448 identification_t *peer)
449 {
450 return eap_ttls_create(server, peer, TRUE);
451 }
452
453 eap_ttls_t *eap_ttls_create_peer(identification_t *server,
454 identification_t *peer)
455 {
456 return eap_ttls_create(server, peer, FALSE);
457 }