generalized tls_eap_t to support EAP_TNC wrapping the TNC_IF_TNCCS protocol
[strongswan.git] / src / libtls / tls_eap.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 "tls_eap.h"
17
18 #include "tls.h"
19
20 #include <debug.h>
21 #include <library.h>
22
23 /** Size limit for a single TLS message */
24 #define MAX_TLS_MESSAGE_LEN 65536
25
26 typedef struct private_tls_eap_t private_tls_eap_t;
27
28 /**
29 * Private data of an tls_eap_t object.
30 */
31 struct private_tls_eap_t {
32
33 /**
34 * Public tls_eap_t interface.
35 */
36 tls_eap_t public;
37
38 /**
39 * Type of EAP method, EAP-TLS or EAP-TTLS
40 */
41 eap_type_t type;
42
43 /**
44 * TLS stack
45 */
46 tls_t *tls;
47
48 /**
49 * Role
50 */
51 bool is_server;
52
53 /**
54 * First fragment of a multi-fragment record?
55 */
56 bool first_fragment;
57
58 /**
59 * Maximum size of an outgoing EAP-TLS fragment
60 */
61 size_t frag_size;
62 };
63
64 /**
65 * Flags of an EAP-TLS/TTLS message
66 */
67 typedef enum {
68 EAP_TLS_LENGTH = (1<<7),
69 EAP_TLS_MORE_FRAGS = (1<<6),
70 EAP_TLS_START = (1<<5),
71 EAP_TTLS_VERSION = (0x07),
72 } eap_tls_flags_t;
73
74 #define EAP_TTLS_SUPPORTED_VERSION 0
75 #define EAP_TNC_SUPPORTED_VERSION 1
76
77 /**
78 * EAP-TLS/TTLS packet format
79 */
80 typedef struct __attribute__((packed)) {
81 u_int8_t code;
82 u_int8_t identifier;
83 u_int16_t length;
84 u_int8_t type;
85 u_int8_t flags;
86 } eap_tls_packet_t;
87
88 METHOD(tls_eap_t, initiate, status_t,
89 private_tls_eap_t *this, chunk_t *out)
90 {
91 if (this->is_server)
92 {
93 eap_tls_packet_t pkt = {
94 .type = this->type,
95 .code = EAP_REQUEST,
96 .flags = EAP_TLS_START,
97 };
98 switch (this->type)
99 {
100 case EAP_TTLS:
101 pkt.flags |= EAP_TTLS_SUPPORTED_VERSION;
102 break;
103 case EAP_TNC:
104 pkt.flags |= EAP_TNC_SUPPORTED_VERSION;
105 break;
106 default:
107 break;
108 }
109 htoun16(&pkt.length, sizeof(eap_tls_packet_t));
110 do
111 { /* start with non-zero random identifier */
112 pkt.identifier = random();
113 }
114 while (!pkt.identifier);
115
116 DBG2(DBG_IKE, "sending %N start packet", eap_type_names, this->type);
117 *out = chunk_clone(chunk_from_thing(pkt));
118 return NEED_MORE;
119 }
120 return FAILED;
121 }
122
123 /**
124 * Process a received packet
125 */
126 static status_t process_pkt(private_tls_eap_t *this, eap_tls_packet_t *pkt)
127 {
128 u_int32_t msg_len;
129 u_int16_t pkt_len;
130
131 pkt_len = untoh16(&pkt->length);
132 if (pkt->flags & EAP_TLS_LENGTH)
133 {
134 if (pkt_len < sizeof(eap_tls_packet_t) + sizeof(msg_len))
135 {
136 DBG1(DBG_TLS, "%N packet too short", eap_type_names, this->type);
137 return FAILED;
138 }
139 msg_len = untoh32(pkt + 1);
140 if (msg_len < pkt_len - sizeof(eap_tls_packet_t) - sizeof(msg_len) ||
141 msg_len > MAX_TLS_MESSAGE_LEN)
142 {
143 DBG1(DBG_TLS, "invalid %N packet length", eap_type_names, this->type);
144 return FAILED;
145 }
146 return this->tls->process(this->tls, (char*)(pkt + 1) + sizeof(msg_len),
147 pkt_len - sizeof(eap_tls_packet_t) - sizeof(msg_len));
148 }
149 return this->tls->process(this->tls, (char*)(pkt + 1),
150 pkt_len - sizeof(eap_tls_packet_t));
151 }
152
153 /**
154 * Build a packet to send
155 */
156 static status_t build_pkt(private_tls_eap_t *this,
157 u_int8_t identifier, chunk_t *out)
158 {
159 char buf[this->frag_size];
160 eap_tls_packet_t *pkt;
161 size_t len, reclen;
162 status_t status;
163 char *kind;
164
165 pkt = (eap_tls_packet_t*)buf;
166 pkt->code = this->is_server ? EAP_REQUEST : EAP_RESPONSE;
167 pkt->identifier = this->is_server ? identifier + 1 : identifier;
168 pkt->type = this->type;
169 pkt->flags = 0;
170
171 switch (this->type)
172 {
173 case EAP_TTLS:
174 pkt->flags |= EAP_TTLS_SUPPORTED_VERSION;
175 break;
176 case EAP_TNC:
177 pkt->flags |= EAP_TNC_SUPPORTED_VERSION;
178 break;
179 default:
180 break;
181 }
182
183 if (this->first_fragment)
184 {
185 pkt->flags |= EAP_TLS_LENGTH;
186 len = sizeof(buf) - sizeof(eap_tls_packet_t) - sizeof(u_int32_t);
187 status = this->tls->build(this->tls, buf + sizeof(eap_tls_packet_t) +
188 sizeof(u_int32_t), &len, &reclen);
189 }
190 else
191 {
192 len = sizeof(buf) - sizeof(eap_tls_packet_t);
193 status = this->tls->build(this->tls, buf + sizeof(eap_tls_packet_t),
194 &len, &reclen);
195 }
196 switch (status)
197 {
198 case NEED_MORE:
199 pkt->flags |= EAP_TLS_MORE_FRAGS;
200 kind = "further fragment";
201 if (this->first_fragment)
202 {
203 this->first_fragment = FALSE;
204 kind = "first fragment";
205 }
206 break;
207 case ALREADY_DONE:
208 kind = "packet";
209 if (!this->first_fragment)
210 {
211 this->first_fragment = TRUE;
212 kind = "final fragment";
213 }
214 break;
215 default:
216 return status;
217 }
218 DBG2(DBG_TLS, "sending %N %s (%u bytes)",
219 eap_type_names, this->type, kind, len);
220 if (reclen)
221 {
222 htoun32(pkt + 1, reclen);
223 len += sizeof(u_int32_t);
224 pkt->flags |= EAP_TLS_LENGTH;
225 }
226 len += sizeof(eap_tls_packet_t);
227 htoun16(&pkt->length, len);
228 *out = chunk_clone(chunk_create(buf, len));
229 return NEED_MORE;
230 }
231
232 /**
233 * Send an ack to request next fragment
234 */
235 static chunk_t create_ack(private_tls_eap_t *this, u_int8_t identifier)
236 {
237 eap_tls_packet_t pkt = {
238 .code = this->is_server ? EAP_REQUEST : EAP_RESPONSE,
239 .identifier = this->is_server ? identifier + 1 : identifier,
240 .type = this->type,
241 };
242 htoun16(&pkt.length, sizeof(pkt));
243 DBG2(DBG_TLS, "sending %N acknowledgement packet",
244 eap_type_names, this->type);
245 return chunk_clone(chunk_from_thing(pkt));
246 }
247
248 METHOD(tls_eap_t, process, status_t,
249 private_tls_eap_t *this, chunk_t in, chunk_t *out)
250 {
251 eap_tls_packet_t *pkt;
252 status_t status;
253
254 pkt = (eap_tls_packet_t*)in.ptr;
255 if (in.len < sizeof(eap_tls_packet_t) ||
256 untoh16(&pkt->length) != in.len)
257 {
258 DBG1(DBG_IKE, "invalid %N packet length",
259 eap_type_names, this->type);
260 return FAILED;
261 }
262 if (pkt->flags & EAP_TLS_START)
263 {
264 if (this->type == EAP_TTLS || this->type == EAP_TNC)
265 {
266 DBG1(DBG_TLS, "%N version is v%u", eap_type_names, this->type,
267 pkt->flags & EAP_TTLS_VERSION);
268 }
269 }
270 else
271 {
272 if (in.len == sizeof(eap_tls_packet_t))
273 {
274 DBG2(DBG_TLS, "received %N acknowledgement packet",
275 eap_type_names, this->type);
276 status = build_pkt(this, pkt->identifier, out);
277 if (status == INVALID_STATE &&
278 this->tls->is_complete(this->tls))
279 {
280 return SUCCESS;
281 }
282 return status;
283 }
284 status = process_pkt(this, pkt);
285 if (status != NEED_MORE)
286 {
287 return status;
288 }
289 }
290 status = build_pkt(this, pkt->identifier, out);
291 switch (status)
292 {
293 case INVALID_STATE:
294 *out = create_ack(this, pkt->identifier);
295 return NEED_MORE;
296 case FAILED:
297 if (!this->is_server)
298 {
299 *out = create_ack(this, pkt->identifier);
300 return NEED_MORE;
301 }
302 return FAILED;
303 default:
304 return status;
305 }
306 }
307
308 METHOD(tls_eap_t, get_msk, chunk_t,
309 private_tls_eap_t *this)
310 {
311 return this->tls->get_eap_msk(this->tls);
312 }
313
314 METHOD(tls_eap_t, destroy, void,
315 private_tls_eap_t *this)
316 {
317 this->tls->destroy(this->tls);
318 free(this);
319 }
320
321 /**
322 * See header
323 */
324 tls_eap_t *tls_eap_create(eap_type_t type, tls_t *tls, size_t frag_size)
325 {
326 private_tls_eap_t *this;
327
328 INIT(this,
329 .public = {
330 .initiate = _initiate,
331 .process = _process,
332 .get_msk = _get_msk,
333 .destroy = _destroy,
334 },
335 .type = type,
336 .is_server = tls->is_server(tls),
337 .first_fragment = TRUE,
338 .frag_size = frag_size,
339 .tls = tls,
340 );
341
342 return &this->public;
343 }