import from HEAD:
[freeradius.git] / src / modules / rlm_eap / types / rlm_eap_sim / rlm_eap_sim.c
1 /*
2  * rlm_eap_sim.c    Handles that are called from eap for SIM
3  *
4  * The development of the EAP/SIM support was funded by Internet Foundation
5  * Austria (http://www.nic.at/ipa).
6  *
7  * Version:     $Id$
8  *
9  *   This program is free software; you can redistribute it and/or modify
10  *   it under the terms of the GNU General Public License as published by
11  *   the Free Software Foundation; either version 2 of the License, or
12  *   (at your option) any later version.
13  *
14  *   This program is distributed in the hope that it will be useful,
15  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
16  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  *   GNU General Public License for more details.
18  *
19  *   You should have received a copy of the GNU General Public License
20  *   along with this program; if not, write to the Free Software
21  *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
22  *
23  * Copyright 2003  Michael Richardson <mcr@sandelman.ottawa.on.ca>
24  * Copyright 2003  The FreeRADIUS server project
25  *
26  */
27
28 #include "autoconf.h"
29
30 #include <stdio.h>
31 #include <stdlib.h>
32
33 #include "../../eap.h"
34 #include "eap_types.h"
35 #include "eap_sim.h"
36
37 #include <rad_assert.h>
38
39 struct eap_sim_server_state {
40         enum eapsim_serverstates state;
41         struct eapsim_keys keys;
42         int  sim_id;
43 };
44
45 /*
46  * Add value pair to reply
47  */
48 static void add_reply(VALUE_PAIR** vp,
49                       const char* name, const char* value, int len)
50 {
51         VALUE_PAIR *reply_attr;
52         reply_attr = pairmake(name, "", T_OP_EQ);
53         if (!reply_attr) {
54                 DEBUG("rlm_eap_sim: "
55                       "add_reply failed to create attribute %s: %s\n",
56                       name, librad_errstr);
57                 return;
58         }
59
60         memcpy(reply_attr->strvalue, value, len);
61         reply_attr->length = len;
62         pairadd(vp, reply_attr);
63 }
64
65 static void eap_sim_state_free(void *opaque)
66 {
67         struct eap_sim_server_state *ess = (struct eap_sim_server_state *)opaque;
68
69         if (!ess) return;
70
71         free(ess);
72 }
73
74 /*
75  *      build a reply to be sent.
76  */
77 static int eap_sim_compose(EAP_HANDLER *handler)
78 {
79         /* we will set the ID on requests, since we have to HMAC it */
80         handler->eap_ds->set_request_id = 1;
81
82         return map_eapsim_basictypes(handler->request->reply,
83                                      handler->eap_ds->request);
84 }
85
86 static int eap_sim_sendstart(EAP_HANDLER *handler)
87 {
88         VALUE_PAIR **vps, *newvp;
89         uint16_t *words;
90         struct eap_sim_server_state *ess;
91
92         rad_assert(handler->request != NULL);
93         rad_assert(handler->request->reply);
94
95         ess = (struct eap_sim_server_state *)handler->opaque;
96
97         /* these are the outgoing attributes */
98         vps = &handler->request->reply->vps;
99
100         rad_assert(vps != NULL);
101
102         /*
103          * add appropriate TLVs for the EAP things we wish to send.
104          */
105
106         /* the version list. We support only version 1. */
107         newvp = paircreate(ATTRIBUTE_EAP_SIM_BASE+PW_EAP_SIM_VERSION_LIST,
108                         PW_TYPE_OCTETS);
109         words = (uint16_t *)newvp->strvalue;
110         newvp->length = 3*sizeof(uint16_t);
111         words[0] = htons(1*sizeof(uint16_t));
112         words[1] = htons(EAP_SIM_VERSION);
113         words[2] = 0;
114         pairadd(vps, newvp);
115
116         /* set the EAP_ID - new value */
117         newvp = paircreate(ATTRIBUTE_EAP_ID, PW_TYPE_INTEGER);
118         newvp->lvalue = ess->sim_id++;
119         pairreplace(vps, newvp);
120
121         /* record it in the ess */
122         ess->keys.versionlistlen = 2;
123         memcpy(ess->keys.versionlist, words+1, ess->keys.versionlistlen);
124
125         /* the ANY_ID attribute. We do not support re-auth or pseudonym */
126         newvp = paircreate(ATTRIBUTE_EAP_SIM_BASE+PW_EAP_SIM_FULLAUTH_ID_REQ,
127                            PW_TYPE_OCTETS);
128         newvp->length = 2;
129         newvp->strvalue[0]=0;
130         newvp->strvalue[0]=1;
131         pairadd(vps, newvp);
132
133         /* the SUBTYPE, set to start. */
134         newvp = paircreate(ATTRIBUTE_EAP_SIM_SUBTYPE, PW_TYPE_INTEGER);
135         newvp->lvalue = eapsim_start;
136         pairreplace(vps, newvp);
137
138         return 1;
139 }
140
141 static int eap_sim_getchalans(VALUE_PAIR *vps, int chalno,
142                               struct eap_sim_server_state *ess)
143 {
144         VALUE_PAIR *vp;
145
146         rad_assert(chalno >= 0 && chalno < 3);
147
148         vp = pairfind(vps, ATTRIBUTE_EAP_SIM_RAND1+chalno);
149         if(vp == NULL) {
150                 /* bad, we can't find stuff! */
151                 DEBUG2("   eap-sim can not find sim-challenge%d",chalno+1);
152                 return 0;
153         }
154         if(vp->length != EAPSIM_RAND_SIZE) {
155                 DEBUG2("   eap-sim chal%d is not 8-bytes: %d", chalno+1,
156                        vp->length);
157                 return 0;
158         }
159         memcpy(ess->keys.rand[chalno], vp->strvalue, EAPSIM_RAND_SIZE);
160
161         vp = pairfind(vps, ATTRIBUTE_EAP_SIM_SRES1+chalno);
162         if(vp == NULL) {
163                 /* bad, we can't find stuff! */
164                 DEBUG2("   eap-sim can not find sim-sres%d",chalno+1);
165                 return 0;
166         }
167         if(vp->length != EAPSIM_SRES_SIZE) {
168                 DEBUG2("   eap-sim sres%d is not 16-bytes: %d", chalno+1,
169                        vp->length);
170                 return 0;
171         }
172         memcpy(ess->keys.sres[chalno], vp->strvalue, EAPSIM_SRES_SIZE);
173
174         vp = pairfind(vps, ATTRIBUTE_EAP_SIM_KC1+chalno);
175         if(vp == NULL) {
176                 /* bad, we can't find stuff! */
177                 DEBUG2("   eap-sim can not find sim-kc%d",chalno+1);
178                 return 0;
179         }
180         if(vp->length != EAPSIM_Kc_SIZE) {
181                 DEBUG2("   eap-sim kc%d is not 8-bytes: %d", chalno+1,
182                        vp->length);
183                 return 0;
184         }
185         memcpy(ess->keys.Kc[chalno], vp->strvalue, EAPSIM_Kc_SIZE);
186
187         return 1;
188 }
189
190 /*
191  * this code sends the challenge itself.
192  *
193  * Challenges will come from one of three places eventually:
194  *
195  * 1  from attributes like ATTRIBUTE_EAP_SIM_RANDx
196  *            (these might be retrived from a database)
197  *
198  * 2  from internally implemented SIM authenticators
199  *            (a simple one based upon XOR will be provided)
200  *
201  * 3  from some kind of SS7 interface.
202  *
203  * For now, they only come from attributes.
204  * It might be that the best way to do 2/3 will be with a different
205  * module to generate/calculate things.
206  *
207  */
208 static int eap_sim_sendchallenge(EAP_HANDLER *handler)
209 {
210         struct eap_sim_server_state *ess;
211         VALUE_PAIR **invps, **outvps, *newvp;
212
213         ess = (struct eap_sim_server_state *)handler->opaque;
214         rad_assert(handler->request != NULL);
215         rad_assert(handler->request->reply);
216
217         /* invps is the data from the client.
218          * but, this is for non-protocol data here. We should
219          * already have consumed any client originated data.
220          */
221         invps = &handler->request->packet->vps;
222
223         /* outvps is the data to the client. */
224         outvps= &handler->request->reply->vps;
225
226         printf("+++> EAP-sim decoded packet:\n");
227         vp_printlist(stdout, *invps);
228
229         /* okay, we got the challenges! Put them into an attribute */
230         newvp = paircreate(ATTRIBUTE_EAP_SIM_BASE+PW_EAP_SIM_RAND,
231                            PW_TYPE_OCTETS);
232         memset(newvp->strvalue,    0, 2); /* clear reserved bytes */
233         memcpy(newvp->strvalue+2+EAPSIM_RAND_SIZE*0, ess->keys.rand[0], EAPSIM_RAND_SIZE);
234         memcpy(newvp->strvalue+2+EAPSIM_RAND_SIZE*1, ess->keys.rand[1], EAPSIM_RAND_SIZE);
235         memcpy(newvp->strvalue+2+EAPSIM_RAND_SIZE*2, ess->keys.rand[2], EAPSIM_RAND_SIZE);
236         newvp->length = 2+EAPSIM_RAND_SIZE*3;
237         pairadd(outvps, newvp);
238
239         /* set the EAP_ID - new value */
240         newvp = paircreate(ATTRIBUTE_EAP_ID, PW_TYPE_INTEGER);
241         newvp->lvalue = ess->sim_id++;
242         pairreplace(outvps, newvp);
243
244         /* make a copy of the identity */
245         ess->keys.identitylen = strlen(handler->identity);
246         memcpy(ess->keys.identity, handler->identity, ess->keys.identitylen);
247
248         /* all set, calculate keys! */
249         eapsim_calculate_keys(&ess->keys);
250
251 #ifdef EAP_SIM_DEBUG_PRF
252         eapsim_dump_mk(&ess->keys);
253 #endif
254
255         /*
256          * need to include an AT_MAC attribute so that it will get
257          * calculated. The NONCE_MT and the MAC are both 16 bytes, so
258          * we store the NONCE_MT in the MAC for the encoder, which
259          * will pull it out before it does the operation.
260          */
261
262         newvp = paircreate(ATTRIBUTE_EAP_SIM_BASE+PW_EAP_SIM_MAC,
263                            PW_TYPE_OCTETS);
264         memcpy(newvp->strvalue, ess->keys.nonce_mt, 16);
265         newvp->length = 16;
266         pairreplace(outvps, newvp);
267
268         newvp = paircreate(ATTRIBUTE_EAP_SIM_KEY, PW_TYPE_OCTETS);
269         memcpy(newvp->strvalue, ess->keys.K_aut, 16);
270         newvp->length = 16;
271         pairreplace(outvps, newvp);
272
273         /* the SUBTYPE, set to challenge. */
274         newvp = paircreate(ATTRIBUTE_EAP_SIM_SUBTYPE, PW_TYPE_INTEGER);
275         newvp->lvalue = eapsim_challenge;
276         pairreplace(outvps, newvp);
277
278         return 1;
279 }
280
281 #ifndef EAPTLS_MPPE_KEY_LEN
282 #define EAPTLS_MPPE_KEY_LEN     32
283 #endif
284
285 /*
286  * this code sends the success message.
287  *
288  * the only work to be done is the add the appropriate SEND/RECV
289  * radius attributes derived from the MSK.
290  *
291  */
292 static int eap_sim_sendsuccess(EAP_HANDLER *handler)
293 {
294         unsigned char *p;
295         struct eap_sim_server_state *ess;
296         VALUE_PAIR **outvps;
297         VALUE_PAIR *newvp;
298
299         /* outvps is the data to the client. */
300         outvps= &handler->request->reply->vps;
301         ess = (struct eap_sim_server_state *)handler->opaque;
302
303         /* set the EAP_ID - new value */
304         newvp = paircreate(ATTRIBUTE_EAP_ID, PW_TYPE_INTEGER);
305         newvp->lvalue = ess->sim_id++;
306         pairreplace(outvps, newvp);
307
308         p = ess->keys.msk;
309         add_reply(outvps, "MS-MPPE-Recv-Key", p, EAPTLS_MPPE_KEY_LEN);
310         p += EAPTLS_MPPE_KEY_LEN;
311         add_reply(outvps, "MS-MPPE-Send-Key", p, EAPTLS_MPPE_KEY_LEN);
312         return 1;
313 }
314
315
316 /*
317  * run the server state machine.
318  */
319 static void eap_sim_stateenter(EAP_HANDLER *handler,
320                                struct eap_sim_server_state *ess,
321                                enum eapsim_serverstates newstate)
322 {
323         switch(newstate) {
324         case eapsim_server_start:
325                 /*
326                  * send the EAP-SIM Start message, listing the
327                  * versions that we support.
328                  */
329                 eap_sim_sendstart(handler);
330                 break;
331
332         case eapsim_server_challenge:
333                 /*
334                  * send the EAP-SIM Challenge message.
335                  */
336                 eap_sim_sendchallenge(handler);
337                 break;
338
339         case eapsim_server_success:
340                 /*
341                  * send the EAP Success message
342                  */
343                 eap_sim_sendsuccess(handler);
344                 handler->eap_ds->request->code = PW_EAP_SUCCESS;
345                 break;
346
347         default:
348                 /*
349                  * nothing to do for this transition.
350                  */
351                 break;
352         }
353
354         ess->state = newstate;
355
356         /* build the target packet */
357         eap_sim_compose(handler);
358 }
359
360 /*
361  *      Initiate the EAP-SIM session by starting the state machine
362  *      and initiating the state.
363  */
364 static int eap_sim_initiate(void *type_data, EAP_HANDLER *handler)
365 {
366         struct eap_sim_server_state *ess;
367         VALUE_PAIR *vp;
368         VALUE_PAIR *outvps;
369         time_t n;
370
371         outvps = handler->request->reply->vps;
372
373         type_data = type_data;  /* shut up compiler */
374
375         vp = pairfind(outvps, ATTRIBUTE_EAP_SIM_RAND1);
376         if(vp == NULL) {
377                 DEBUG2("   can not initiate sim, no RAND1 attribute");
378                 return 0;
379         }
380
381         ess = malloc(sizeof(struct eap_sim_server_state));
382         if(ess == NULL) {
383                 DEBUG2("   no space for eap sim state");
384                 return 0;
385         }
386
387         handler->opaque = ((void *)ess);
388         handler->free_opaque = eap_sim_state_free;
389
390         handler->stage = AUTHENTICATE;
391
392         /*
393          * save the keying material, because it could change on a subsequent
394          * retrival.
395          *
396          */
397         if((eap_sim_getchalans(outvps, 0, ess) +
398             eap_sim_getchalans(outvps, 1, ess) +
399             eap_sim_getchalans(outvps, 2, ess)) != 3)
400         {
401                 DEBUG2("   can not initiate sim, missing attributes");
402                 return 0;
403         }
404
405         /*
406          * this value doesn't have be strong, but it is good if it
407          * is different now and then
408          */
409         time(&n);
410         ess->sim_id = (n & 0xff);
411
412         eap_sim_stateenter(handler, ess, eapsim_server_start);
413
414         return 1;
415 }
416
417
418 /*
419  * process an EAP-Sim/Response/Start.
420  *
421  * verify that client chose a version, and provided a NONCE_MT,
422  * and if so, then change states to challenge, and send the new
423  * challenge, else, resend the Request/Start.
424  *
425  */
426 static int process_eap_sim_start(EAP_HANDLER *handler, VALUE_PAIR *vps)
427 {
428         VALUE_PAIR *nonce_vp, *selectedversion_vp;
429         struct eap_sim_server_state *ess;
430         uint16_t simversion;
431
432         ess = (struct eap_sim_server_state *)handler->opaque;
433
434         nonce_vp = pairfind(vps, ATTRIBUTE_EAP_SIM_BASE+PW_EAP_SIM_NONCE_MT);
435         selectedversion_vp = pairfind(vps, ATTRIBUTE_EAP_SIM_BASE+PW_EAP_SIM_SELECTED_VERSION);
436
437         if(nonce_vp == NULL ||
438            selectedversion_vp == NULL) {
439                 DEBUG2("   client did not select a version and send a NONCE");
440                 eap_sim_stateenter(handler, ess, eapsim_server_start);
441                 return 1;
442         }
443
444         /*
445          * okay, good got stuff that we need. Check the version we found.
446          */
447         if(selectedversion_vp->length < 2) {
448                 DEBUG2("   EAP-Sim version field is too short.");
449                 return 0;
450         }
451         memcpy(&simversion, selectedversion_vp->strvalue, sizeof(simversion));
452         simversion = ntohs(simversion);
453         if(simversion != EAP_SIM_VERSION) {
454                 DEBUG2("   EAP-Sim version %d is unknown.", simversion);
455                 return 0;
456         }
457
458         /* record it for later keying */
459         memcpy(ess->keys.versionselect, selectedversion_vp->strvalue,
460                sizeof(ess->keys.versionselect));
461
462         /*
463          * double check the nonce size.
464          */
465         if(nonce_vp->length != 18) {
466                 DEBUG2("   EAP-Sim nonce_mt must be 16 bytes (+2 bytes padding), not %d", nonce_vp->length);
467                 return 0;
468         }
469         memcpy(ess->keys.nonce_mt, nonce_vp->strvalue+2, 16);
470
471         /* everything looks good, change states */
472         eap_sim_stateenter(handler, ess, eapsim_server_challenge);
473         return 1;
474 }
475
476
477 /*
478  * process an EAP-Sim/Response/Challenge
479  *
480  * verify that MAC that we received matches what we would have
481  * calculated from the packet with the SRESx appended.
482  *
483  */
484 static int process_eap_sim_challenge(EAP_HANDLER *handler, VALUE_PAIR *vps)
485 {
486         struct eap_sim_server_state *ess;
487         unsigned char srescat[EAPSIM_SRES_SIZE*3];
488         unsigned char calcmac[EAPSIM_CALCMAC_SIZE];
489
490         ess = (struct eap_sim_server_state *)handler->opaque;
491
492         memcpy(srescat +(0*EAPSIM_SRES_SIZE), ess->keys.sres[0], EAPSIM_SRES_SIZE);
493         memcpy(srescat +(1*EAPSIM_SRES_SIZE), ess->keys.sres[1], EAPSIM_SRES_SIZE);
494         memcpy(srescat +(2*EAPSIM_SRES_SIZE), ess->keys.sres[2], EAPSIM_SRES_SIZE);
495
496         /* verify the MAC, now that we have all the keys. */
497         if(eapsim_checkmac(vps, ess->keys.K_aut,
498                            srescat, sizeof(srescat),
499                            calcmac)) {
500                 DEBUG2("MAC check succeed\n");
501         } else {
502                 int i, j;
503                 unsigned char macline[20*3];
504                 char *m = macline;
505
506                 j=0;
507                 for (i = 0; i < EAPSIM_CALCMAC_SIZE; i++) {
508                         if(j==4) {
509                           *m++ = '_';
510                           j=0;
511                         }
512                         j++;
513
514                         sprintf(m, "%02x", calcmac[i]);
515                         m = m + strlen(m);
516                 }
517                 DEBUG2("calculated MAC (%s) did not match", macline);
518                 return 0;
519         }
520
521         /* everything looks good, change states */
522         eap_sim_stateenter(handler, ess, eapsim_server_success);
523         return 1;
524 }
525
526
527 /*
528  *      Authenticate a previously sent challenge.
529  */
530 static int eap_sim_authenticate(void *arg, EAP_HANDLER *handler)
531 {
532         struct eap_sim_server_state *ess;
533         VALUE_PAIR *vp, *vps;
534         enum eapsim_subtype subtype;
535         int success;
536
537         arg = arg; /* shut up compiler */
538
539         ess = (struct eap_sim_server_state *)handler->opaque;
540
541         /* vps is the data from the client */
542         vps = handler->request->packet->vps;
543
544         success= unmap_eapsim_basictypes(handler->request->packet,
545                                          handler->eap_ds->response->type.data,
546                                          handler->eap_ds->response->type.length);
547
548         if(!success) {
549           return 0;
550         }
551
552         /* see what kind of message we have gotten */
553         if((vp = pairfind(vps, ATTRIBUTE_EAP_SIM_SUBTYPE)) == NULL)
554         {
555                 DEBUG2("   no subtype attribute was created, message dropped");
556                 return 0;
557         }
558         subtype = vp->lvalue;
559
560         switch(ess->state) {
561         case eapsim_server_start:
562                 switch(subtype) {
563                 default:
564                         /*
565                          * pretty much anything else here is illegal,
566                          * so we will retransmit the request.
567                          */
568                         eap_sim_stateenter(handler, ess, eapsim_server_start);
569                         return 1;
570
571                 case eapsim_start:
572                         /*
573                          * a response to our EAP-Sim/Request/Start!
574                          *
575                          */
576                         return process_eap_sim_start(handler, vps);
577                 }
578                 break;
579         case eapsim_server_challenge:
580                 switch(subtype) {
581                 default:
582                         /*
583                          * pretty much anything else here is illegal,
584                          * so we will retransmit the request.
585                          */
586                         eap_sim_stateenter(handler, ess, eapsim_server_challenge);
587                         return 1;
588
589                 case eapsim_challenge:
590                         /*
591                          * a response to our EAP-Sim/Request/Challenge!
592                          *
593                          */
594                         return process_eap_sim_challenge(handler, vps);
595                 }
596                 break;
597
598         default:
599                 /* if we get into some other state, die, as this
600                  * is a coding error!
601                  */
602                 DEBUG2("  illegal-unknown state reached in eap_sim_authenticate\n");
603                 abort();
604         }
605
606         return 0;
607 }
608
609 /*
610  *      The module name should be the only globally exported symbol.
611  *      That is, everything else should be 'static'.
612  */
613 EAP_TYPE rlm_eap_sim = {
614         "eap_sim",
615         NULL,                           /* XXX attach */
616         eap_sim_initiate,               /* Start the initial request */
617         NULL,                           /* XXX authorization */
618         eap_sim_authenticate,           /* authentication */
619         NULL                            /* XXX detach */
620 };
621
622 /*
623  * $Log$
624  * Revision 1.12  2004-03-19 02:20:35  mcr
625  *      increment the EAP-id on each stage of the transaction.
626  *
627  * Revision 1.11  2004/02/26 19:04:31  aland
628  *      perl -i -npe "s/[ \t]+$//g" `find src -name "*.[ch]" -print`
629  *
630  *      Whitespace changes only, from a fresh checkout.
631  *
632  *      For bug # 13
633  *
634  * Revision 1.10  2004/01/30 20:35:33  mcr
635  *      capture the RAND/SRES/Kc when we initialize the SIM
636  *      rather than later, when they may have changed.
637  *
638  * Revision 1.9  2004/01/30 19:38:29  mcr
639  *      added some debugging of why EAP-sim might not want to
640  *      handle the request - lacking RAND1 attribute.
641  *
642  * Revision 1.8  2003/12/29 01:13:43  mcr
643  *      if the un-marshalling fails, then fail the packet.
644  *
645  * Revision 1.7  2003/11/22 00:21:17  mcr
646  *      send the encryption keys to the AccessPoint.
647  *
648  * Revision 1.6  2003/11/22 00:10:18  mcr
649  *      the version list attribute's length of versions is in bytes,
650  *      not entries.
651  *
652  * Revision 1.5  2003/11/21 19:15:51  mcr
653  *      rename "SIM-Chal" to "SIM-Rand" to sync with names in official
654  *      documentation.
655  *
656  * Revision 1.4  2003/11/21 19:02:19  mcr
657  *      pack the RAND attribute properly - should have 2 bytes
658  * reserved.
659  *
660  * Revision 1.3  2003/11/06 15:45:12  aland
661  *      u_int -> uint
662  *
663  * Revision 1.2  2003/10/31 22:33:45  mcr
664  *      fixes for version list length types.
665  *      do not include length in hash.
666  *      use defines rather than constant sizes.
667  *
668  * Revision 1.1  2003/10/29 02:49:19  mcr
669  *      initial commit of eap-sim
670  *
671  * Revision 1.3  2003/09/14 00:44:42  mcr
672  *      finished trivial challenge state.
673  *
674  *
675  * Local Variables:
676  * c-file-style: "linux"
677  * End Variables:
678  *
679  */