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