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