optional certificate-based peer authentication on TLS server side
[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 DBG2(DBG_IKE, "sending EAP-TLS start packet");
124
125 *out = eap_payload_create_data(chunk_from_thing(pkt));
126 return NEED_MORE;
127 }
128 return FAILED;
129 }
130
131 /**
132 * Write received TLS data to the input buffer
133 */
134 static bool write_buf(private_eap_tls_t *this, eap_tls_packet_t *pkt)
135 {
136 u_int32_t msg_len;
137 u_int16_t pkt_len;
138 chunk_t data;
139
140 pkt_len = untoh16(&pkt->length);
141
142 if (pkt->flags & EAP_TLS_LENGTH)
143 {
144 if (pkt_len < sizeof(eap_tls_packet_t) + sizeof(msg_len))
145 {
146 DBG1(DBG_IKE, "EAP-TLS packet too short");
147 return FALSE;
148 }
149 msg_len = untoh32(pkt + 1);
150 if (msg_len < pkt_len - sizeof(eap_tls_packet_t) - sizeof(msg_len) ||
151 msg_len > MAX_TLS_MESSAGE_LEN)
152 {
153 DBG1(DBG_IKE, "invalid EAP-TLS packet length");
154 return FALSE;
155 }
156 if (this->input.ptr)
157 {
158 if (msg_len != this->input.len)
159 {
160 DBG1(DBG_IKE, "received unexpected TLS message length");
161 return FALSE;
162 }
163 }
164 else
165 {
166 this->input = chunk_alloc(msg_len);
167 this->inpos = 0;
168 }
169 data = chunk_create((char*)(pkt + 1) + sizeof(msg_len),
170 pkt_len - sizeof(eap_tls_packet_t) - sizeof(msg_len));
171 }
172 else
173 {
174 data = chunk_create((char*)(pkt + 1),
175 pkt_len - sizeof(eap_tls_packet_t));
176 }
177 if (data.len > this->input.len - this->inpos)
178 {
179 DBG1(DBG_IKE, "EAP-TLS fragment exceeds TLS message length");
180 return FALSE;
181 }
182 memcpy(this->input.ptr + this->inpos, data.ptr, data.len);
183 this->inpos += data.len;
184 return TRUE;
185 }
186
187 /**
188 * Send an ack to request next fragment
189 */
190 static eap_payload_t *create_ack(private_eap_tls_t *this, u_int8_t identifier)
191 {
192 eap_tls_packet_t pkt = {
193 .code = this->is_server ? EAP_REQUEST : EAP_RESPONSE,
194 .identifier = this->is_server ? identifier + 1 : identifier,
195 .type = EAP_TLS,
196 };
197 htoun16(&pkt.length, sizeof(pkt));
198 DBG2(DBG_IKE, "sending EAP-TLS acknowledgement packet");
199
200 return eap_payload_create_data(chunk_from_thing(pkt));
201 }
202
203 /**
204 * Create a eap response from data in the TLS output buffer
205 */
206 static eap_payload_t *read_buf(private_eap_tls_t *this, u_int8_t identifier)
207 {
208 char buf[EAP_TLS_FRAGMENT_LEN + sizeof(eap_tls_packet_t) + 4], *start;
209 eap_tls_packet_t *pkt = (eap_tls_packet_t*)buf;
210 u_int16_t pkt_len = sizeof(eap_tls_packet_t);
211
212 pkt->code = this->is_server ? EAP_REQUEST : EAP_RESPONSE;
213 pkt->identifier = this->is_server ? identifier + 1 : identifier;
214 pkt->type = EAP_TLS;
215 pkt->flags = 0;
216
217 if (this->output.len)
218 {
219 start = (char*)(pkt + 1);
220 if (this->outpos == 0)
221 { /* first fragment */
222 pkt->flags = EAP_TLS_LENGTH;
223 pkt_len += 4;
224 start += 4;
225 htoun32(pkt + 1, this->output.len);
226 }
227
228 if (this->output.len - this->outpos > EAP_TLS_FRAGMENT_LEN)
229 {
230 pkt->flags |= EAP_TLS_MORE_FRAGS;
231 pkt_len += EAP_TLS_FRAGMENT_LEN;
232 memcpy(start, this->output.ptr + this->outpos, EAP_TLS_FRAGMENT_LEN);
233 this->outpos += EAP_TLS_FRAGMENT_LEN;
234 DBG2(DBG_IKE, "sending EAP-TLS packet fragment");
235 }
236 else
237 {
238 pkt_len += this->output.len - this->outpos;
239 memcpy(start, this->output.ptr + this->outpos,
240 this->output.len - this->outpos);
241 chunk_free(&this->output);
242 this->outpos = 0;
243 DBG2(DBG_IKE, "sending EAP-TLS packet");
244 }
245 }
246 else
247 {
248 DBG2(DBG_IKE, "sending EAP-TLS acknowledgement packet");
249 }
250 htoun16(&pkt->length, pkt_len);
251 return eap_payload_create_data(chunk_create(buf, pkt_len));
252 }
253
254 /**
255 * Pass data in input buffer to upper layers, write result to output buffer
256 */
257 static status_t process_buf(private_eap_tls_t *this)
258 {
259 tls_record_t *in, out;
260 chunk_t data;
261 u_int16_t len;
262 status_t status;
263
264 /* pass input buffer to upper layer, record for record */
265 data = this->input;
266 while (data.len > sizeof(tls_record_t))
267 {
268 in = (tls_record_t*)data.ptr;
269 len = untoh16(&in->length);
270 if (len > data.len - sizeof(tls_record_t))
271 {
272 DBG1(DBG_IKE, "TLS record length invalid");
273 return FAILED;
274 }
275 if (untoh16(&in->version) < TLS_1_0)
276 {
277 DBG1(DBG_IKE, "%N invalid with EAP-TLS",
278 tls_version_names, untoh16(&in->version));
279 return FAILED;
280 }
281
282 status = this->tls->process(this->tls, in->type,
283 chunk_create(in->data, len));
284 if (status != NEED_MORE)
285 {
286 return status;
287 }
288 data = chunk_skip(data, len + sizeof(tls_record_t));
289 }
290 chunk_free(&this->input);
291 this->inpos = 0;
292
293 /* read in records from upper layer, append to output buffer */
294 chunk_free(&this->output);
295 while (TRUE)
296 {
297 tls_content_type_t type;
298 chunk_t header = chunk_from_thing(out);
299
300 status = this->tls->build(this->tls, &type, &data);
301 switch (status)
302 {
303 case NEED_MORE:
304 break;
305 case INVALID_STATE:
306 /* invalid state means we need more input from peer first */
307 return NEED_MORE;
308 case SUCCESS:
309 return SUCCESS;
310 case FAILED:
311 default:
312 return FAILED;
313 }
314 out.type = type;
315 htoun16(&out.version, this->tls->get_version(this->tls));
316 htoun16(&out.length, data.len);
317 this->output = chunk_cat("mcm", this->output, header, data);
318 }
319 }
320
321 METHOD(eap_method_t, process, status_t,
322 private_eap_tls_t *this, eap_payload_t *in, eap_payload_t **out)
323 {
324 eap_tls_packet_t *pkt;
325 chunk_t data;
326 status_t status;
327
328 if (++this->processed > MAX_EAP_TLS_MESSAGE_COUNT)
329 {
330 DBG1(DBG_IKE, "EAP-TLS packet count exceeded");
331 return FAILED;
332 }
333
334 data = in->get_data(in);
335
336 pkt = (eap_tls_packet_t*)data.ptr;
337 if (data.len < sizeof(eap_tls_packet_t) ||
338 untoh16(&pkt->length) != data.len)
339 {
340 DBG1(DBG_IKE, "invalid EAP-TLS packet length");
341 return FAILED;
342 }
343 if (!(pkt->flags & EAP_TLS_START))
344 {
345 if (data.len == sizeof(eap_tls_packet_t))
346 {
347 if (this->output.len)
348 { /* ACK to our fragment, send next */
349 *out = read_buf(this, pkt->identifier);
350 return NEED_MORE;
351 }
352 if (this->tls->is_complete(this->tls))
353 {
354 return SUCCESS;
355 }
356 return FAILED;
357 }
358 if (!write_buf(this, pkt))
359 {
360 return FAILED;
361 }
362 if (pkt->flags & EAP_TLS_MORE_FRAGS)
363 { /* more fragments follow */
364 *out = create_ack(this, pkt->identifier);
365 return NEED_MORE;
366 }
367 else if (this->input.len != this->inpos)
368 {
369 DBG1(DBG_IKE, "defragmented TLS message has invalid length");
370 return FAILED;
371 }
372 }
373 status = process_buf(this);
374 if (status == NEED_MORE)
375 {
376 *out = read_buf(this, pkt->identifier);
377 }
378 return status;
379 }
380
381 METHOD(eap_method_t, get_type, eap_type_t,
382 private_eap_tls_t *this, u_int32_t *vendor)
383 {
384 *vendor = 0;
385 return EAP_TLS;
386 }
387
388 METHOD(eap_method_t, get_msk, status_t,
389 private_eap_tls_t *this, chunk_t *msk)
390 {
391 *msk = this->tls->get_eap_msk(this->tls);
392 if (msk->len)
393 {
394 return SUCCESS;
395 }
396 return FAILED;
397 }
398
399 METHOD(eap_method_t, is_mutual, bool,
400 private_eap_tls_t *this)
401 {
402 return TRUE;
403 }
404
405 METHOD(eap_method_t, destroy, void,
406 private_eap_tls_t *this)
407 {
408 free(this->input.ptr);
409 free(this->output.ptr);
410
411 this->tls->destroy(this->tls);
412
413 free(this);
414 }
415
416 /**
417 * Generic private constructor
418 */
419 static eap_tls_t *eap_tls_create(identification_t *server,
420 identification_t *peer, bool is_server)
421 {
422 private_eap_tls_t *this;
423
424 INIT(this,
425 .public.eap_method = {
426 .initiate = _initiate,
427 .process = _process,
428 .get_type = _get_type,
429 .is_mutual = _is_mutual,
430 .get_msk = _get_msk,
431 .destroy = _destroy,
432 },
433 .is_server = is_server,
434 );
435 /* MSK PRF ASCII constant label according to EAP-TLS RFC 5216 */
436 this->tls = tls_create(is_server, server, peer, TRUE,
437 "client EAP encryption", NULL);
438
439 return &this->public;
440 }
441
442 eap_tls_t *eap_tls_create_server(identification_t *server,
443 identification_t *peer)
444 {
445 return eap_tls_create(server, peer, TRUE);
446 }
447
448 eap_tls_t *eap_tls_create_peer(identification_t *server,
449 identification_t *peer)
450 {
451 return eap_tls_create(server, peer, FALSE);
452 }