Use the EAP-SIM/AKA crypto helper in EAP-SIM
[strongswan.git] / src / charon / plugins / eap_sim / eap_sim_peer.c
1 /*
2 * Copyright (C) 2007-2009 Martin Willi
3 * Hochschule fuer Technik Rapperswil
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_sim_peer.h"
17
18 #include <daemon.h>
19
20 #include <simaka_message.h>
21
22 /* number of tries we do authenticate */
23 #define MAX_TRIES 3
24
25 /* number of triplets for one authentication */
26 #define TRIPLET_COUNT 3
27
28 /** length of the AT_NONCE_MT/AT_NONCE_S nonce value */
29 #define NONCE_LEN 16
30 /** length of the AT_MAC value */
31 #define MAC_LEN 16
32 /** length of the AT_RAND value */
33 #define RAND_LEN 16
34 /** length of Kc */
35 #define KC_LEN 8
36 /** length of SRES */
37 #define SRES_LEN 4
38
39 typedef struct private_eap_sim_peer_t private_eap_sim_peer_t;
40
41 /**
42 * Private data of an eap_sim_peer_t object.
43 */
44 struct private_eap_sim_peer_t {
45
46 /**
47 * Public authenticator_t interface.
48 */
49 eap_sim_peer_t public;
50
51 /**
52 * permanent ID of peer
53 */
54 identification_t *peer;
55
56 /**
57 * EAP-SIM crypto helper
58 */
59 simaka_crypto_t *crypto;
60
61 /**
62 * how many times we try to authenticate
63 */
64 int tries;
65
66 /**
67 * version list received from server
68 */
69 chunk_t version_list;
70
71 /**
72 * Nonce value used in AT_NONCE_MT/AT_NONCE_S
73 */
74 chunk_t nonce;
75
76 /**
77 * MSK, used for EAP-SIM based IKEv2 authentication
78 */
79 chunk_t msk;
80 };
81
82 /* version of SIM protocol we speak */
83 static chunk_t version = chunk_from_chars(0x00,0x01);
84 /* client error codes used in AT_CLIENT_ERROR_CODE */
85 static chunk_t client_error_general = chunk_from_chars(0x00, 0x01);
86 static chunk_t client_error_unsupported = chunk_from_chars(0x00, 0x02);
87 static chunk_t client_error_insufficient = chunk_from_chars(0x00, 0x03);
88
89 /**
90 * Read a triplet from the SIM card
91 */
92 static bool get_card_triplet(private_eap_sim_peer_t *this,
93 char *rand, char *sres, char *kc)
94 {
95 enumerator_t *enumerator;
96 sim_card_t *card;
97 bool success = FALSE;
98
99 enumerator = charon->sim->create_card_enumerator(charon->sim);
100 while (enumerator->enumerate(enumerator, &card))
101 {
102 if (card->get_triplet(card, this->peer, rand, sres, kc))
103 {
104 success = TRUE;
105 break;
106 }
107 }
108 enumerator->destroy(enumerator);
109 if (!success)
110 {
111 DBG1(DBG_IKE, "no SIM card found with triplets for '%Y'", this->peer);
112 }
113 return success;
114 }
115
116 /**
117 * Send a SIM_CLIENT_ERROR
118 */
119 static eap_payload_t* create_client_error(private_eap_sim_peer_t *this,
120 u_int8_t identifier, chunk_t code)
121 {
122 simaka_message_t *message;
123 eap_payload_t *out;
124
125 message = simaka_message_create(FALSE, identifier,
126 EAP_SIM, SIM_CLIENT_ERROR);
127 message->add_attribute(message, AT_CLIENT_ERROR_CODE, code);
128 out = message->generate(message, this->crypto, chunk_empty);
129 message->destroy(message);
130 return out;
131 }
132
133 /**
134 * process an EAP-SIM/Request/Start message
135 */
136 static status_t process_start(private_eap_sim_peer_t *this,
137 simaka_message_t *in, eap_payload_t **out)
138 {
139 simaka_message_t *message;
140 enumerator_t *enumerator;
141 simaka_attribute_t type;
142 chunk_t data;
143 rng_t *rng;
144 bool supported = FALSE;
145
146 enumerator = in->create_attribute_enumerator(in);
147 while (enumerator->enumerate(enumerator, &type, &data))
148 {
149 switch (type)
150 {
151 case AT_VERSION_LIST:
152 {
153 free(this->version_list.ptr);
154 this->version_list = chunk_clone(data);
155 while (data.len >= version.len)
156 {
157 if (memeq(data.ptr, version.ptr, version.len))
158 {
159 supported = TRUE;
160 break;
161 }
162 }
163 break;
164 }
165 default:
166 DBG1(DBG_IKE, "ignoring EAP-SIM attribute %N",
167 simaka_attribute_names, type);
168 break;
169 }
170 }
171 enumerator->destroy(enumerator);
172
173 if (!supported)
174 {
175 DBG1(DBG_IKE, "server does not support EAP-SIM version number 1");
176 *out = create_client_error(this, in->get_identifier(in),
177 client_error_unsupported);
178 return NEED_MORE;
179 }
180
181 /* generate AT_NONCE_MT value */
182 rng = this->crypto->get_rng(this->crypto);
183 free(this->nonce.ptr);
184 rng->allocate_bytes(rng, NONCE_LEN, &this->nonce);
185
186 message = simaka_message_create(FALSE, in->get_identifier(in),
187 EAP_SIM, SIM_START);
188 message->add_attribute(message, AT_SELECTED_VERSION, version);
189 message->add_attribute(message, AT_NONCE_MT, this->nonce);
190 *out = message->generate(message, this->crypto, chunk_empty);
191 message->destroy(message);
192
193 return NEED_MORE;
194 }
195
196 /**
197 * process an EAP-SIM/Request/Challenge message
198 */
199 static status_t process_challenge(private_eap_sim_peer_t *this,
200 simaka_message_t *in, eap_payload_t **out)
201 {
202 simaka_message_t *message;
203 enumerator_t *enumerator;
204 simaka_attribute_t type;
205 chunk_t data, rands = chunk_empty, kcs, kc, sreses, sres;
206
207 if (this->tries-- <= 0)
208 {
209 /* give up without notification. This hack is required as some buggy
210 * server implementations won't respect our client-error. */
211 return FAILED;
212 }
213
214 enumerator = in->create_attribute_enumerator(in);
215 while (enumerator->enumerate(enumerator, &type, &data))
216 {
217 switch (type)
218 {
219 case AT_RAND:
220 {
221 rands = data;
222 break;
223 }
224 default:
225 DBG1(DBG_IKE, "ignoring EAP-SIM attribute %N",
226 simaka_attribute_names, type);
227 break;
228 }
229 }
230 enumerator->destroy(enumerator);
231
232 /* excepting two or three RAND, each 16 bytes. We require two valid
233 * and different RANDs */
234 if ((rands.len != 2 * RAND_LEN && rands.len != 3 * RAND_LEN) ||
235 memeq(rands.ptr, rands.ptr + RAND_LEN, RAND_LEN))
236 {
237 DBG1(DBG_IKE, "no valid AT_RAND received");
238 *out = create_client_error(this, in->get_identifier(in),
239 client_error_insufficient);
240 return NEED_MORE;
241 }
242 /* get two or three KCs/SRESes from SIM using RANDs */
243 kcs = kc = chunk_alloca(rands.len / 2);
244 sreses = sres = chunk_alloca(rands.len / 4);
245 while (rands.len >= RAND_LEN)
246 {
247 if (!get_card_triplet(this, rands.ptr, sres.ptr, kc.ptr))
248 {
249 DBG1(DBG_IKE, "unable to get EAP-SIM triplet");
250 *out = create_client_error(this, in->get_identifier(in),
251 client_error_general);
252 return NEED_MORE;
253 }
254 DBG3(DBG_IKE, "got triplet for RAND %b\n Kc %b\n SRES %b",
255 rands.ptr, RAND_LEN, sres.ptr, SRES_LEN, kc.ptr, KC_LEN);
256 kc = chunk_skip(kc, KC_LEN);
257 sres = chunk_skip(sres, SRES_LEN);
258 rands = chunk_skip(rands, RAND_LEN);
259 }
260
261 data = chunk_cata("cccc", kcs, this->nonce, this->version_list, version);
262 free(this->msk.ptr);
263 this->msk = this->crypto->derive_keys_full(this->crypto, this->peer, data);
264
265 /* verify AT_MAC attribute, signature is over "EAP packet | NONCE_MT" */
266 if (!in->verify(in, this->crypto, this->nonce))
267 {
268 DBG1(DBG_IKE, "AT_MAC verification failed");
269 *out = create_client_error(this, in->get_identifier(in),
270 client_error_general);
271 return NEED_MORE;
272 }
273
274 /* build response with AT_MAC, built over "EAP packet | n*SRES" */
275 message = simaka_message_create(FALSE, in->get_identifier(in),
276 EAP_SIM, SIM_CHALLENGE);
277 *out = message->generate(message, this->crypto, sreses);
278 message->destroy(message);
279 return NEED_MORE;
280 }
281
282 /**
283 * process an EAP-SIM/Request/Notification message
284 */
285 static status_t process_notification(private_eap_sim_peer_t *this,
286 simaka_message_t *in, eap_payload_t **out)
287 {
288 simaka_message_t *message;
289 enumerator_t *enumerator;
290 simaka_attribute_t type;
291 chunk_t data;
292 bool success = TRUE;
293
294 enumerator = in->create_attribute_enumerator(in);
295 while (enumerator->enumerate(enumerator, &type, &data))
296 {
297 if (type == AT_NOTIFICATION)
298 {
299 /* test success bit */
300 if (!(data.ptr[0] & 0x80))
301 {
302 success = FALSE;
303 DBG1(DBG_IKE, "received EAP-SIM notification error %#B", &data);
304 }
305 else
306 {
307 DBG1(DBG_IKE, "received EAP-SIM notification code %#B", &data);
308 }
309 }
310 }
311 enumerator->destroy(enumerator);
312
313 if (success)
314 { /* empty notification reply */
315 message = simaka_message_create(FALSE, in->get_identifier(in),
316 EAP_SIM, SIM_NOTIFICATION);
317 *out = message->generate(message, this->crypto, chunk_empty);
318 message->destroy(message);
319 }
320 else
321 {
322 *out = create_client_error(this, in->get_identifier(in),
323 client_error_general);
324 }
325 return NEED_MORE;
326 }
327
328 /**
329 * Implementation of eap_method_t.process
330 */
331 static status_t process(private_eap_sim_peer_t *this,
332 eap_payload_t *in, eap_payload_t **out)
333 {
334 simaka_message_t *message;
335 status_t status;
336
337 message = simaka_message_create_from_payload(in);
338 if (!message)
339 {
340 *out = create_client_error(this, in->get_identifier(in),
341 client_error_general);
342 return NEED_MORE;
343 }
344 if (!message->parse(message, this->crypto))
345 {
346 message->destroy(message);
347 *out = create_client_error(this, in->get_identifier(in),
348 client_error_general);
349 return NEED_MORE;
350 }
351 switch (message->get_subtype(message))
352 {
353 case SIM_START:
354 status = process_start(this, message, out);
355 break;
356 case SIM_CHALLENGE:
357 status = process_challenge(this, message, out);
358 break;
359 case SIM_NOTIFICATION:
360 status = process_notification(this, message, out);
361 break;
362 default:
363 *out = create_client_error(this, in->get_identifier(in),
364 client_error_general);
365 status = NEED_MORE;
366 break;
367 }
368 message->destroy(message);
369 return status;
370 }
371
372 /**
373 * Implementation of eap_method_t.initiate
374 */
375 static status_t initiate(private_eap_sim_peer_t *this, eap_payload_t **out)
376 {
377 /* peer never initiates */
378 return FAILED;
379 }
380
381 /**
382 * Implementation of eap_method_t.get_type.
383 */
384 static eap_type_t get_type(private_eap_sim_peer_t *this, u_int32_t *vendor)
385 {
386 *vendor = 0;
387 return EAP_SIM;
388 }
389
390 /**
391 * Implementation of eap_method_t.get_msk.
392 */
393 static status_t get_msk(private_eap_sim_peer_t *this, chunk_t *msk)
394 {
395 if (this->msk.ptr)
396 {
397 *msk = this->msk;
398 return SUCCESS;
399 }
400 return FAILED;
401 }
402
403 /**
404 * Implementation of eap_method_t.is_mutual.
405 */
406 static bool is_mutual(private_eap_sim_peer_t *this)
407 {
408 return TRUE;
409 }
410
411 /**
412 * Implementation of eap_method_t.destroy.
413 */
414 static void destroy(private_eap_sim_peer_t *this)
415 {
416 this->peer->destroy(this->peer);
417 this->crypto->destroy(this->crypto);
418 free(this->version_list.ptr);
419 free(this->nonce.ptr);
420 free(this->msk.ptr);
421 free(this);
422 }
423
424 /*
425 * Described in header.
426 */
427 eap_sim_peer_t *eap_sim_peer_create(identification_t *server,
428 identification_t *peer)
429 {
430 private_eap_sim_peer_t *this = malloc_thing(private_eap_sim_peer_t);
431
432 this->public.interface.initiate = (status_t(*)(eap_method_t*,eap_payload_t**))initiate;
433 this->public.interface.process = (status_t(*)(eap_method_t*,eap_payload_t*,eap_payload_t**))process;
434 this->public.interface.get_type = (eap_type_t(*)(eap_method_t*,u_int32_t*))get_type;
435 this->public.interface.is_mutual = (bool(*)(eap_method_t*))is_mutual;
436 this->public.interface.get_msk = (status_t(*)(eap_method_t*,chunk_t*))get_msk;
437 this->public.interface.destroy = (void(*)(eap_method_t*))destroy;
438
439 this->crypto = simaka_crypto_create();
440 if (!this->crypto)
441 {
442 free(this);
443 return NULL;
444 }
445 this->peer = peer->clone(peer);
446 this->tries = MAX_TRIES;
447 this->version_list = chunk_empty;
448 this->nonce = chunk_empty;
449 this->msk = chunk_empty;
450
451 return &this->public;
452 }
453