]> git.ipfire.org Git - thirdparty/strongswan.git/blob - src/libcharon/plugins/eap_tls/eap_tls.c
EAP-TLS and EAP-TTLS use different constant MSK PRF label
[thirdparty/strongswan.git] / src / libcharon / plugins / eap_tls / eap_tls.c
1 /*
2 * Copyright (C) 2010 Martin Willi
3 * Copyright (C) 2010 revosec AG
4 *
5 * This program is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License as published by the
7 * Free Software Foundation; either version 2 of the License, or (at your
8 * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
9 *
10 * This program is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
13 * for more details.
14 */
15
16 #include "eap_tls.h"
17
18 #include <tls.h>
19
20 #include <daemon.h>
21 #include <library.h>
22
23 typedef struct private_eap_tls_t private_eap_tls_t;
24
25 /**
26 * Private data of an eap_tls_t object.
27 */
28 struct private_eap_tls_t {
29
30 /**
31 * Public interface.
32 */
33 eap_tls_t public;
34
35 /**
36 * Number of EAP-TLS messages processed so far
37 */
38 int processed;
39
40 /**
41 * Is this method instance acting as server?
42 */
43 bool is_server;
44
45 /**
46 * TLS layers
47 */
48 tls_t *tls;
49
50 /**
51 * Allocated input buffer
52 */
53 chunk_t input;
54
55 /**
56 * Number of bytes read in input buffer
57 */
58 size_t inpos;
59
60 /**
61 * Allocated ouput buffer
62 */
63 chunk_t output;
64
65 /**
66 * Number of bytes sent from output buffer
67 */
68 size_t outpos;
69 };
70
71 /** Size limit for a single TLS message */
72 #define MAX_TLS_MESSAGE_LEN 16384
73 /** Size of a EAP-TLS fragment */
74 #define EAP_TLS_FRAGMENT_LEN 1014
75 /** Maximum number of EAP-TLS messages/fragments allowed */
76 #define MAX_EAP_TLS_MESSAGE_COUNT 16
77
78 /**
79 * Flags of an EAP-TLS message
80 */
81 typedef enum {
82 EAP_TLS_LENGTH = (1<<7),
83 EAP_TLS_MORE_FRAGS = (1<<6),
84 EAP_TLS_START = (1<<5),
85 } eap_tls_flags_t;
86
87 /**
88 * EAP-TLS packet format
89 */
90 typedef struct __attribute__((packed)) {
91 u_int8_t code;
92 u_int8_t identifier;
93 u_int16_t length;
94 u_int8_t type;
95 u_int8_t flags;
96 } eap_tls_packet_t;
97
98 /**
99 * TLS record
100 */
101 typedef struct __attribute__((packed)) {
102 u_int8_t type;
103 u_int16_t version;
104 u_int16_t length;
105 char data[];
106 } tls_record_t;
107
108 METHOD(eap_method_t, initiate, status_t,
109 private_eap_tls_t *this, eap_payload_t **out)
110 {
111 if (this->is_server)
112 {
113 eap_tls_packet_t pkt = {
114 .type = EAP_TLS,
115 .code = EAP_REQUEST,
116 .flags = EAP_TLS_START,
117 };
118 htoun16(&pkt.length, sizeof(eap_tls_packet_t));
119 /* start with non-zero random identifier */
120 do {
121 pkt.identifier = random();
122 } while (!pkt.identifier);
123
124 *out = eap_payload_create_data(chunk_from_thing(pkt));
125 return NEED_MORE;
126 }
127 return FAILED;
128 }
129
130 /**
131 * Write received TLS data to the input buffer
132 */
133 static bool write_buf(private_eap_tls_t *this, eap_tls_packet_t *pkt)
134 {
135 u_int32_t msg_len;
136 u_int16_t pkt_len;
137 chunk_t data;
138
139 pkt_len = untoh16(&pkt->length);
140
141 if (pkt->flags & EAP_TLS_LENGTH)
142 {
143 if (pkt_len < sizeof(eap_tls_packet_t) + sizeof(msg_len))
144 {
145 DBG1(DBG_IKE, "EAP-TLS packet too short");
146 return FALSE;
147 }
148 msg_len = untoh32(pkt + 1);
149 if (msg_len < pkt_len - sizeof(eap_tls_packet_t) - sizeof(msg_len) ||
150 msg_len > MAX_TLS_MESSAGE_LEN)
151 {
152 DBG1(DBG_IKE, "invalid EAP-TLS packet length");
153 return FALSE;
154 }
155 if (this->input.ptr)
156 {
157 if (msg_len != this->input.len)
158 {
159 DBG1(DBG_IKE, "received unexpected TLS message length");
160 return FALSE;
161 }
162 }
163 else
164 {
165 this->input = chunk_alloc(msg_len);
166 this->inpos = 0;
167 }
168 data = chunk_create((char*)(pkt + 1) + sizeof(msg_len),
169 pkt_len - sizeof(eap_tls_packet_t) - sizeof(msg_len));
170 }
171 else
172 {
173 data = chunk_create((char*)(pkt + 1),
174 pkt_len - sizeof(eap_tls_packet_t));
175 }
176 if (data.len > this->input.len - this->inpos)
177 {
178 DBG1(DBG_IKE, "EAP-TLS fragment exceeds TLS message length");
179 return FALSE;
180 }
181 memcpy(this->input.ptr + this->inpos, data.ptr, data.len);
182 this->inpos += data.len;
183 return TRUE;
184 }
185
186 /**
187 * Send an ack to request next fragment
188 */
189 static eap_payload_t *create_ack(private_eap_tls_t *this, u_int8_t identifier)
190 {
191 eap_tls_packet_t pkt = {
192 .code = this->is_server ? EAP_REQUEST : EAP_RESPONSE,
193 .identifier = this->is_server ? identifier + 1 : identifier,
194 .type = EAP_TLS,
195 };
196 htoun16(&pkt.length, sizeof(pkt));
197
198 return eap_payload_create_data(chunk_from_thing(pkt));
199 }
200
201 /**
202 * Create a eap response from data in the TLS output buffer
203 */
204 static eap_payload_t *read_buf(private_eap_tls_t *this, u_int8_t identifier)
205 {
206 char buf[EAP_TLS_FRAGMENT_LEN + sizeof(eap_tls_packet_t) + 4], *start;
207 eap_tls_packet_t *pkt = (eap_tls_packet_t*)buf;
208 u_int16_t pkt_len = sizeof(eap_tls_packet_t);
209
210 pkt->code = this->is_server ? EAP_REQUEST : EAP_RESPONSE;
211 pkt->identifier = this->is_server ? identifier + 1 : identifier;
212 pkt->type = EAP_TLS;
213 pkt->flags = 0;
214
215 if (this->output.len)
216 {
217 start = (char*)(pkt + 1);
218 if (this->outpos == 0)
219 { /* first fragment */
220 pkt->flags = EAP_TLS_LENGTH;
221 pkt_len += 4;
222 start += 4;
223 htoun32(pkt + 1, this->output.len);
224 }
225
226 if (this->output.len - this->outpos > EAP_TLS_FRAGMENT_LEN)
227 {
228 pkt->flags |= EAP_TLS_MORE_FRAGS;
229 pkt_len += EAP_TLS_FRAGMENT_LEN;
230 memcpy(start, this->output.ptr + this->outpos, EAP_TLS_FRAGMENT_LEN);
231 this->outpos += EAP_TLS_FRAGMENT_LEN;
232 }
233 else
234 {
235 pkt_len += this->output.len - this->outpos;
236 memcpy(start, this->output.ptr + this->outpos,
237 this->output.len - this->outpos);
238 chunk_free(&this->output);
239 this->outpos = 0;
240 }
241 }
242 htoun16(&pkt->length, pkt_len);
243 return eap_payload_create_data(chunk_create(buf, pkt_len));
244 }
245
246 /**
247 * Pass data in input buffer to upper layers, write result to output buffer
248 */
249 static status_t process_buf(private_eap_tls_t *this)
250 {
251 tls_record_t *in, out;
252 chunk_t data;
253 u_int16_t len;
254 status_t status;
255
256 /* pass input buffer to upper layer, record for record */
257 data = this->input;
258 while (data.len > sizeof(tls_record_t))
259 {
260 in = (tls_record_t*)data.ptr;
261 len = untoh16(&in->length);
262 if (len > data.len - sizeof(tls_record_t))
263 {
264 DBG1(DBG_IKE, "TLS record length invalid");
265 return FAILED;
266 }
267 if (untoh16(&in->version) < TLS_1_0)
268 {
269 DBG1(DBG_IKE, "%N invalid with EAP-TLS",
270 tls_version_names, untoh16(&in->version));
271 return FAILED;
272 }
273
274 status = this->tls->process(this->tls, in->type,
275 chunk_create(in->data, len));
276 if (status != NEED_MORE)
277 {
278 return status;
279 }
280 data = chunk_skip(data, len + sizeof(tls_record_t));
281 }
282 chunk_free(&this->input);
283 this->inpos = 0;
284
285 /* read in records from upper layer, append to output buffer */
286 chunk_free(&this->output);
287 while (TRUE)
288 {
289 tls_content_type_t type;
290 chunk_t header = chunk_from_thing(out);
291
292 status = this->tls->build(this->tls, &type, &data);
293 switch (status)
294 {
295 case NEED_MORE:
296 break;
297 case INVALID_STATE:
298 /* invalid state means we need more input from peer first */
299 return NEED_MORE;
300 case SUCCESS:
301 return SUCCESS;
302 case FAILED:
303 default:
304 return FAILED;
305 }
306 out.type = type;
307 htoun16(&out.version, this->tls->get_version(this->tls));
308 htoun16(&out.length, data.len);
309 this->output = chunk_cat("mcm", this->output, header, data);
310 }
311 }
312
313 METHOD(eap_method_t, process, status_t,
314 private_eap_tls_t *this, eap_payload_t *in, eap_payload_t **out)
315 {
316 eap_tls_packet_t *pkt;
317 chunk_t data;
318 status_t status;
319
320 if (++this->processed > MAX_EAP_TLS_MESSAGE_COUNT)
321 {
322 DBG1(DBG_IKE, "EAP-TLS packet count exceeded");
323 return FAILED;
324 }
325
326 data = in->get_data(in);
327
328 pkt = (eap_tls_packet_t*)data.ptr;
329 if (data.len < sizeof(eap_tls_packet_t) ||
330 untoh16(&pkt->length) != data.len)
331 {
332 DBG1(DBG_IKE, "invalid EAP-TLS packet length");
333 return FAILED;
334 }
335 if (!(pkt->flags & EAP_TLS_START))
336 {
337 if (data.len == sizeof(eap_tls_packet_t))
338 {
339 if (this->output.len)
340 { /* ACK to our fragment, send next */
341 *out = read_buf(this, pkt->identifier);
342 return NEED_MORE;
343 }
344 if (this->tls->is_complete(this->tls))
345 {
346 return SUCCESS;
347 }
348 return FAILED;
349 }
350 if (!write_buf(this, pkt))
351 {
352 return FAILED;
353 }
354 if (pkt->flags & EAP_TLS_MORE_FRAGS)
355 { /* more fragments follow */
356 *out = create_ack(this, pkt->identifier);
357 return NEED_MORE;
358 }
359 else if (this->input.len != this->inpos)
360 {
361 DBG1(DBG_IKE, "defragemented TLS message has invalid length");
362 return FAILED;
363 }
364 }
365 status = process_buf(this);
366 if (status == NEED_MORE)
367 {
368 *out = read_buf(this, pkt->identifier);
369 }
370 return status;
371 }
372
373 METHOD(eap_method_t, get_type, eap_type_t,
374 private_eap_tls_t *this, u_int32_t *vendor)
375 {
376 *vendor = 0;
377 return EAP_TLS;
378 }
379
380 METHOD(eap_method_t, get_msk, status_t,
381 private_eap_tls_t *this, chunk_t *msk)
382 {
383 *msk = this->tls->get_eap_msk(this->tls);
384 if (msk->len)
385 {
386 return SUCCESS;
387 }
388 return FAILED;
389 }
390
391 METHOD(eap_method_t, is_mutual, bool,
392 private_eap_tls_t *this)
393 {
394 return TRUE;
395 }
396
397 METHOD(eap_method_t, destroy, void,
398 private_eap_tls_t *this)
399 {
400 free(this->input.ptr);
401 free(this->output.ptr);
402
403 this->tls->destroy(this->tls);
404
405 free(this);
406 }
407
408 /**
409 * Generic private constructor
410 */
411 static eap_tls_t *eap_tls_create(identification_t *server,
412 identification_t *peer, bool is_server)
413 {
414 private_eap_tls_t *this;
415
416 INIT(this,
417 .public.eap_method = {
418 .initiate = _initiate,
419 .process = _process,
420 .get_type = _get_type,
421 .is_mutual = _is_mutual,
422 .get_msk = _get_msk,
423 .destroy = _destroy,
424 },
425 .is_server = is_server,
426 );
427 /* MSK PRF ASCII constant label according to EAP-TLS RFC 5216 */
428 this->tls = tls_create(is_server, server, peer, "client EAP encryption");
429
430 return &this->public;
431 }
432
433 eap_tls_t *eap_tls_create_server(identification_t *server,
434 identification_t *peer)
435 {
436 return eap_tls_create(server, peer, TRUE);
437 }
438
439 eap_tls_t *eap_tls_create_peer(identification_t *server,
440 identification_t *peer)
441 {
442 return eap_tls_create(server, peer, FALSE);
443 }