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