max_message_count = 0 disables limit
[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_int32_t msg_len;
154 u_int16_t pkt_len;
155
156 pkt_len = untoh16(&pkt->length);
157 if (pkt->flags & EAP_TLS_LENGTH)
158 {
159 if (pkt_len < sizeof(eap_tls_packet_t) + sizeof(msg_len))
160 {
161 DBG1(DBG_TLS, "%N packet too short", eap_type_names, this->type);
162 return FAILED;
163 }
164 msg_len = untoh32(pkt + 1);
165 if (msg_len < pkt_len - sizeof(eap_tls_packet_t) - sizeof(msg_len) ||
166 msg_len > MAX_TLS_MESSAGE_LEN)
167 {
168 DBG1(DBG_TLS, "invalid %N packet length (%u bytes)", eap_type_names,
169 this->type, msg_len);
170 return FAILED;
171 }
172 return this->tls->process(this->tls, (char*)(pkt + 1) + sizeof(msg_len),
173 pkt_len - sizeof(eap_tls_packet_t) - sizeof(msg_len));
174 }
175 return this->tls->process(this->tls, (char*)(pkt + 1),
176 pkt_len - sizeof(eap_tls_packet_t));
177 }
178
179 /**
180 * Build a packet to send
181 */
182 static status_t build_pkt(private_tls_eap_t *this, chunk_t *out)
183 {
184 char buf[this->frag_size];
185 eap_tls_packet_t *pkt;
186 size_t len, reclen;
187 status_t status;
188 char *kind;
189
190 if (this->is_server)
191 {
192 this->identifier++;
193 }
194 pkt = (eap_tls_packet_t*)buf;
195 pkt->code = this->is_server ? EAP_REQUEST : EAP_RESPONSE;
196 pkt->identifier = this->identifier;
197 pkt->type = this->type;
198 pkt->flags = 0;
199
200 switch (this->type)
201 {
202 case EAP_TTLS:
203 pkt->flags |= EAP_TTLS_SUPPORTED_VERSION;
204 break;
205 case EAP_TNC:
206 pkt->flags |= EAP_TNC_SUPPORTED_VERSION;
207 break;
208 case EAP_PEAP:
209 pkt->flags |= EAP_PEAP_SUPPORTED_VERSION;
210 break;
211 default:
212 break;
213 }
214
215 if (this->first_fragment)
216 {
217 len = sizeof(buf) - sizeof(eap_tls_packet_t) - sizeof(u_int32_t);
218 status = this->tls->build(this->tls, buf + sizeof(eap_tls_packet_t) +
219 sizeof(u_int32_t), &len, &reclen);
220 }
221 else
222 {
223 len = sizeof(buf) - sizeof(eap_tls_packet_t);
224 status = this->tls->build(this->tls, buf + sizeof(eap_tls_packet_t),
225 &len, &reclen);
226 }
227 switch (status)
228 {
229 case NEED_MORE:
230 pkt->flags |= EAP_TLS_MORE_FRAGS;
231 kind = "further fragment";
232 if (this->first_fragment)
233 {
234 pkt->flags |= EAP_TLS_LENGTH;
235 this->first_fragment = FALSE;
236 kind = "first fragment";
237 }
238 break;
239 case ALREADY_DONE:
240 if (this->first_fragment)
241 {
242 if (this->include_length)
243 {
244 pkt->flags |= EAP_TLS_LENGTH;
245 }
246 kind = "packet";
247 }
248 else
249 {
250 this->first_fragment = TRUE;
251 kind = "final fragment";
252 }
253 break;
254 default:
255 return status;
256 }
257 if (reclen)
258 {
259 if (pkt->flags & EAP_TLS_LENGTH)
260 {
261 htoun32(pkt + 1, reclen);
262 len += sizeof(u_int32_t);
263 pkt->flags |= EAP_TLS_LENGTH;
264 }
265 else
266 {
267 /* get rid of the reserved length field */
268 memcpy(buf+sizeof(eap_packet_t),
269 buf+sizeof(eap_packet_t)+sizeof(u_int32_t), len);
270 }
271 }
272 len += sizeof(eap_tls_packet_t);
273 htoun16(&pkt->length, len);
274 *out = chunk_clone(chunk_create(buf, len));
275 DBG2(DBG_TLS, "sending %N %s (%u bytes)",
276 eap_type_names, this->type, kind, len);
277 DBG3(DBG_TLS, "%B", out);
278 return NEED_MORE;
279 }
280
281 /**
282 * Send an ack to request next fragment
283 */
284 static chunk_t create_ack(private_tls_eap_t *this)
285 {
286 eap_tls_packet_t pkt = {
287 .code = this->is_server ? EAP_REQUEST : EAP_RESPONSE,
288 .type = this->type,
289 };
290
291 if (this->is_server)
292 {
293 this->identifier++;
294 }
295 pkt.identifier = this->identifier;
296 htoun16(&pkt.length, sizeof(pkt));
297
298 switch (this->type)
299 {
300 case EAP_TTLS:
301 pkt.flags |= EAP_TTLS_SUPPORTED_VERSION;
302 break;
303 case EAP_TNC:
304 pkt.flags |= EAP_TNC_SUPPORTED_VERSION;
305 break;
306 case EAP_PEAP:
307 pkt.flags |= EAP_PEAP_SUPPORTED_VERSION;
308 break;
309 default:
310 break;
311 }
312 DBG2(DBG_TLS, "sending %N acknowledgement packet",
313 eap_type_names, this->type);
314 return chunk_clone(chunk_from_thing(pkt));
315 }
316
317 METHOD(tls_eap_t, process, status_t,
318 private_tls_eap_t *this, chunk_t in, chunk_t *out)
319 {
320 eap_tls_packet_t *pkt;
321 status_t status;
322
323 if (this->max_msg_count && ++this->processed > this->max_msg_count)
324 {
325 DBG1(DBG_TLS, "%N packet count exceeded (%d > %d)",
326 eap_type_names, this->type,
327 this->processed, this->max_msg_count);
328 return FAILED;
329 }
330
331 pkt = (eap_tls_packet_t*)in.ptr;
332 if (in.len < sizeof(eap_tls_packet_t) || untoh16(&pkt->length) != in.len)
333 {
334 DBG1(DBG_TLS, "invalid %N packet length", eap_type_names, this->type);
335 return FAILED;
336 }
337
338 /* update EAP identifier */
339 if (!this->is_server)
340 {
341 this->identifier = pkt->identifier;
342 }
343 DBG3(DBG_TLS, "%N payload %B", eap_type_names, this->type, &in);
344
345 if (pkt->flags & EAP_TLS_START)
346 {
347 if (this->type == EAP_TTLS || this->type == EAP_TNC ||
348 this->type == EAP_PEAP)
349 {
350 DBG1(DBG_TLS, "%N version is v%u", eap_type_names, this->type,
351 pkt->flags & EAP_TTLS_VERSION);
352 }
353 }
354 else
355 {
356 if (in.len == sizeof(eap_tls_packet_t))
357 {
358 DBG2(DBG_TLS, "received %N acknowledgement packet",
359 eap_type_names, this->type);
360 status = build_pkt(this, out);
361 if (status == INVALID_STATE && this->tls->is_complete(this->tls))
362 {
363 return SUCCESS;
364 }
365 return status;
366 }
367 status = process_pkt(this, pkt);
368 switch (status)
369 {
370 case NEED_MORE:
371 break;
372 case SUCCESS:
373 return this->tls->is_complete(this->tls) ? SUCCESS : FAILED;
374 default:
375 return status;
376 }
377 }
378 status = build_pkt(this, out);
379 switch (status)
380 {
381 case INVALID_STATE:
382 *out = create_ack(this);
383 return NEED_MORE;
384 case FAILED:
385 if (!this->is_server)
386 {
387 *out = create_ack(this);
388 return NEED_MORE;
389 }
390 return FAILED;
391 default:
392 return status;
393 }
394 }
395
396 METHOD(tls_eap_t, get_msk, chunk_t,
397 private_tls_eap_t *this)
398 {
399 return this->tls->get_eap_msk(this->tls);
400 }
401
402 METHOD(tls_eap_t, get_identifier, u_int8_t,
403 private_tls_eap_t *this)
404 {
405 return this->identifier;
406 }
407
408 METHOD(tls_eap_t, set_identifier, void,
409 private_tls_eap_t *this, u_int8_t identifier)
410 {
411 this->identifier = identifier;
412 }
413
414 METHOD(tls_eap_t, destroy, void,
415 private_tls_eap_t *this)
416 {
417 this->tls->destroy(this->tls);
418 free(this);
419 }
420
421 /**
422 * See header
423 */
424 tls_eap_t *tls_eap_create(eap_type_t type, tls_t *tls, size_t frag_size,
425 int max_msg_count, bool include_length)
426 {
427 private_tls_eap_t *this;
428
429 if (!tls)
430 {
431 return NULL;
432 }
433
434 INIT(this,
435 .public = {
436 .initiate = _initiate,
437 .process = _process,
438 .get_msk = _get_msk,
439 .get_identifier = _get_identifier,
440 .set_identifier = _set_identifier,
441 .destroy = _destroy,
442 },
443 .type = type,
444 .is_server = tls->is_server(tls),
445 .first_fragment = TRUE,
446 .frag_size = frag_size,
447 .max_msg_count = max_msg_count,
448 .include_length = include_length,
449 .tls = tls,
450 );
451
452 if (this->is_server)
453 {
454 do
455 { /* start with non-zero random identifier */
456 this->identifier = random();
457 }
458 while (!this->identifier);
459 }
460
461 return &this->public;
462 }