some tls_eap optimizations
[strongswan.git] / src / libtls / tls_eap.c
1
2 /*
3 * Copyright (C) 2010 Martin Willi
4 * Copyright (C) 2010 revosec AG
5 *
6 * This program is free software; you can redistribute it and/or modify it
7 * under the terms of the GNU General Public License as published by the
8 * Free Software Foundation; either version 2 of the License, or (at your
9 * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
10 *
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
13 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * for more details.
15 */
16
17 #include "tls_eap.h"
18
19 #include "tls.h"
20
21 #include <debug.h>
22 #include <library.h>
23
24 /** Size limit for a single TLS message */
25 #define MAX_TLS_MESSAGE_LEN 65536
26
27 typedef struct private_tls_eap_t private_tls_eap_t;
28
29 /**
30 * Private data of an tls_eap_t object.
31 */
32 struct private_tls_eap_t {
33
34 /**
35 * Public tls_eap_t interface.
36 */
37 tls_eap_t public;
38
39 /**
40 * Type of EAP method, EAP-TLS, EAP-TTLS, or EAP-TNC
41 */
42 eap_type_t type;
43
44 /**
45 * Current value of EAP identifier
46 */
47 u_int8_t identifier;
48
49 /**
50 * TLS stack
51 */
52 tls_t *tls;
53
54 /**
55 * Role
56 */
57 bool is_server;
58
59 /**
60 * If FALSE include the total length of an EAP message
61 * in the first fragment of fragmented messages only.
62 * If TRUE also include the length in non-fragmented messages.
63 */
64 bool include_length;
65
66 /**
67 * First fragment of a multi-fragment record?
68 */
69 bool first_fragment;
70
71 /**
72 * Maximum size of an outgoing EAP-TLS fragment
73 */
74 size_t frag_size;
75
76 /**
77 * Number of EAP messages/fragments processed so far
78 */
79 int processed;
80
81 /**
82 * Maximum number of processed EAP messages/fragments
83 */
84 int max_msg_count;
85 };
86
87 /**
88 * Flags of an EAP-TLS/TTLS/TNC message
89 */
90 typedef enum {
91 EAP_TLS_LENGTH = (1<<7), /* shared with EAP-TTLS/TNC/PEAP */
92 EAP_TLS_MORE_FRAGS = (1<<6), /* shared with EAP-TTLS/TNC/PEAP */
93 EAP_TLS_START = (1<<5), /* shared with EAP-TTLS/TNC/PEAP */
94 EAP_TTLS_VERSION = (0x07), /* shared with EAP-TNC/PEAP */
95 } eap_tls_flags_t;
96
97 #define EAP_TTLS_SUPPORTED_VERSION 0
98 #define EAP_TNC_SUPPORTED_VERSION 1
99 #define EAP_PEAP_SUPPORTED_VERSION 0
100
101 /**
102 * EAP-TLS/TTLS packet format
103 */
104 typedef struct __attribute__((packed)) {
105 u_int8_t code;
106 u_int8_t identifier;
107 u_int16_t length;
108 u_int8_t type;
109 u_int8_t flags;
110 } eap_tls_packet_t;
111
112 METHOD(tls_eap_t, initiate, status_t,
113 private_tls_eap_t *this, chunk_t *out)
114 {
115 if (this->is_server)
116 {
117 eap_tls_packet_t pkt = {
118 .type = this->type,
119 .code = EAP_REQUEST,
120 .flags = EAP_TLS_START,
121 };
122 switch (this->type)
123 {
124 case EAP_TTLS:
125 pkt.flags |= EAP_TTLS_SUPPORTED_VERSION;
126 break;
127 case EAP_TNC:
128 pkt.flags |= EAP_TNC_SUPPORTED_VERSION;
129 break;
130 case EAP_PEAP:
131 pkt.flags |= EAP_PEAP_SUPPORTED_VERSION;
132 break;
133 default:
134 break;
135 }
136 htoun16(&pkt.length, sizeof(eap_tls_packet_t));
137 pkt.identifier = this->identifier;
138
139 *out = chunk_clone(chunk_from_thing(pkt));
140 DBG2(DBG_TLS, "sending %N start packet (%u bytes)",
141 eap_type_names, this->type, sizeof(eap_tls_packet_t));
142 DBG3(DBG_TLS, "%B", out);
143 return NEED_MORE;
144 }
145 return FAILED;
146 }
147
148 /**
149 * Process a received packet
150 */
151 static status_t process_pkt(private_tls_eap_t *this, eap_tls_packet_t *pkt)
152 {
153 u_int16_t pkt_len;
154 u_int32_t msg_len;
155 size_t msg_len_offset = 0;
156
157 pkt_len = untoh16(&pkt->length);
158
159 if (pkt->flags & EAP_TLS_LENGTH)
160 {
161 if (pkt_len < sizeof(eap_tls_packet_t) + sizeof(msg_len))
162 {
163 DBG1(DBG_TLS, "%N packet too short", eap_type_names, this->type);
164 return FAILED;
165 }
166 msg_len = untoh32(pkt + 1);
167 if (msg_len < pkt_len - sizeof(eap_tls_packet_t) - sizeof(msg_len) ||
168 msg_len > MAX_TLS_MESSAGE_LEN)
169 {
170 DBG1(DBG_TLS, "invalid %N packet length (%u bytes)", eap_type_names,
171 this->type, msg_len);
172 return FAILED;
173 }
174 msg_len_offset = sizeof(msg_len);
175 }
176
177 return this->tls->process(this->tls, (char*)(pkt + 1) + msg_len_offset,
178 pkt_len - sizeof(eap_tls_packet_t) - msg_len_offset);
179 }
180
181 /**
182 * Build a packet to send
183 */
184 static status_t build_pkt(private_tls_eap_t *this, chunk_t *out)
185 {
186 char buf[this->frag_size];
187 eap_tls_packet_t *pkt;
188 size_t len, reclen, msg_len_offset;
189 status_t status;
190 char *kind;
191
192 if (this->is_server)
193 {
194 this->identifier++;
195 }
196 pkt = (eap_tls_packet_t*)buf;
197 pkt->code = this->is_server ? EAP_REQUEST : EAP_RESPONSE;
198 pkt->identifier = this->identifier;
199 pkt->type = this->type;
200 pkt->flags = 0;
201
202 switch (this->type)
203 {
204 case EAP_TTLS:
205 pkt->flags |= EAP_TTLS_SUPPORTED_VERSION;
206 break;
207 case EAP_TNC:
208 pkt->flags |= EAP_TNC_SUPPORTED_VERSION;
209 break;
210 case EAP_PEAP:
211 pkt->flags |= EAP_PEAP_SUPPORTED_VERSION;
212 break;
213 default:
214 break;
215 }
216
217 if (this->first_fragment)
218 {
219 len = sizeof(buf) - sizeof(eap_tls_packet_t) - sizeof(u_int32_t);
220 msg_len_offset = sizeof(u_int32_t);
221 }
222 else
223 {
224 len = sizeof(buf) - sizeof(eap_tls_packet_t);
225 msg_len_offset = 0;
226 }
227 status = this->tls->build(this->tls, buf + sizeof(eap_tls_packet_t) +
228 msg_len_offset, &len, &reclen);
229
230 switch (status)
231 {
232 case NEED_MORE:
233 pkt->flags |= EAP_TLS_MORE_FRAGS;
234 kind = "further fragment";
235 if (this->first_fragment)
236 {
237 pkt->flags |= EAP_TLS_LENGTH;
238 this->first_fragment = FALSE;
239 kind = "first fragment";
240 }
241 break;
242 case ALREADY_DONE:
243 if (this->first_fragment)
244 {
245 if (this->include_length)
246 {
247 pkt->flags |= EAP_TLS_LENGTH;
248 }
249 kind = "packet";
250 }
251 else
252 {
253 this->first_fragment = TRUE;
254 kind = "final fragment";
255 }
256 break;
257 default:
258 return status;
259 }
260 if (reclen)
261 {
262 if (pkt->flags & EAP_TLS_LENGTH)
263 {
264 htoun32(pkt + 1, reclen);
265 len += sizeof(u_int32_t);
266 pkt->flags |= EAP_TLS_LENGTH;
267 }
268 else
269 {
270 /* get rid of the reserved length field */
271 memcpy(buf+sizeof(eap_packet_t),
272 buf+sizeof(eap_packet_t)+sizeof(u_int32_t), len);
273 }
274 }
275 len += sizeof(eap_tls_packet_t);
276 htoun16(&pkt->length, len);
277 *out = chunk_clone(chunk_create(buf, len));
278 DBG2(DBG_TLS, "sending %N %s (%u bytes)",
279 eap_type_names, this->type, kind, len);
280 DBG3(DBG_TLS, "%B", out);
281 return NEED_MORE;
282 }
283
284 /**
285 * Send an ack to request next fragment
286 */
287 static chunk_t create_ack(private_tls_eap_t *this)
288 {
289 eap_tls_packet_t pkt = {
290 .code = this->is_server ? EAP_REQUEST : EAP_RESPONSE,
291 .type = this->type,
292 };
293
294 if (this->is_server)
295 {
296 this->identifier++;
297 }
298 pkt.identifier = this->identifier;
299 htoun16(&pkt.length, sizeof(pkt));
300
301 switch (this->type)
302 {
303 case EAP_TTLS:
304 pkt.flags |= EAP_TTLS_SUPPORTED_VERSION;
305 break;
306 case EAP_TNC:
307 pkt.flags |= EAP_TNC_SUPPORTED_VERSION;
308 break;
309 case EAP_PEAP:
310 pkt.flags |= EAP_PEAP_SUPPORTED_VERSION;
311 break;
312 default:
313 break;
314 }
315 DBG2(DBG_TLS, "sending %N acknowledgement packet",
316 eap_type_names, this->type);
317 return chunk_clone(chunk_from_thing(pkt));
318 }
319
320 METHOD(tls_eap_t, process, status_t,
321 private_tls_eap_t *this, chunk_t in, chunk_t *out)
322 {
323 eap_tls_packet_t *pkt;
324 status_t status;
325
326 if (this->max_msg_count && ++this->processed > this->max_msg_count)
327 {
328 DBG1(DBG_TLS, "%N packet count exceeded (%d > %d)",
329 eap_type_names, this->type,
330 this->processed, this->max_msg_count);
331 return FAILED;
332 }
333
334 pkt = (eap_tls_packet_t*)in.ptr;
335 if (in.len < sizeof(eap_tls_packet_t) || untoh16(&pkt->length) != in.len)
336 {
337 DBG1(DBG_TLS, "invalid %N packet length", eap_type_names, this->type);
338 return FAILED;
339 }
340
341 /* update EAP identifier */
342 if (!this->is_server)
343 {
344 this->identifier = pkt->identifier;
345 }
346 DBG3(DBG_TLS, "%N payload %B", eap_type_names, this->type, &in);
347
348 if (pkt->flags & EAP_TLS_START)
349 {
350 if (this->type == EAP_TTLS || this->type == EAP_TNC ||
351 this->type == EAP_PEAP)
352 {
353 DBG1(DBG_TLS, "%N version is v%u", eap_type_names, this->type,
354 pkt->flags & EAP_TTLS_VERSION);
355 }
356 }
357 else
358 {
359 if (in.len == sizeof(eap_tls_packet_t))
360 {
361 DBG2(DBG_TLS, "received %N acknowledgement packet",
362 eap_type_names, this->type);
363 status = build_pkt(this, out);
364 if (status == INVALID_STATE && this->tls->is_complete(this->tls))
365 {
366 return SUCCESS;
367 }
368 return status;
369 }
370 status = process_pkt(this, pkt);
371 switch (status)
372 {
373 case NEED_MORE:
374 break;
375 case SUCCESS:
376 return this->tls->is_complete(this->tls) ? SUCCESS : FAILED;
377 default:
378 return status;
379 }
380 }
381 status = build_pkt(this, out);
382 switch (status)
383 {
384 case INVALID_STATE:
385 *out = create_ack(this);
386 return NEED_MORE;
387 case FAILED:
388 if (!this->is_server)
389 {
390 *out = create_ack(this);
391 return NEED_MORE;
392 }
393 return FAILED;
394 default:
395 return status;
396 }
397 }
398
399 METHOD(tls_eap_t, get_msk, chunk_t,
400 private_tls_eap_t *this)
401 {
402 return this->tls->get_eap_msk(this->tls);
403 }
404
405 METHOD(tls_eap_t, get_identifier, u_int8_t,
406 private_tls_eap_t *this)
407 {
408 return this->identifier;
409 }
410
411 METHOD(tls_eap_t, set_identifier, void,
412 private_tls_eap_t *this, u_int8_t identifier)
413 {
414 this->identifier = identifier;
415 }
416
417 METHOD(tls_eap_t, destroy, void,
418 private_tls_eap_t *this)
419 {
420 this->tls->destroy(this->tls);
421 free(this);
422 }
423
424 /**
425 * See header
426 */
427 tls_eap_t *tls_eap_create(eap_type_t type, tls_t *tls, size_t frag_size,
428 int max_msg_count, bool include_length)
429 {
430 private_tls_eap_t *this;
431
432 if (!tls)
433 {
434 return NULL;
435 }
436
437 INIT(this,
438 .public = {
439 .initiate = _initiate,
440 .process = _process,
441 .get_msk = _get_msk,
442 .get_identifier = _get_identifier,
443 .set_identifier = _set_identifier,
444 .destroy = _destroy,
445 },
446 .type = type,
447 .is_server = tls->is_server(tls),
448 .first_fragment = TRUE,
449 .frag_size = frag_size,
450 .max_msg_count = max_msg_count,
451 .include_length = include_length,
452 .tls = tls,
453 );
454
455 if (this->is_server)
456 {
457 do
458 { /* start with non-zero random identifier */
459 this->identifier = random();
460 }
461 while (!this->identifier);
462 }
463
464 return &this->public;
465 }