port fix from branch_1_1
[freeradius.git] / src / modules / rlm_eap / libeap / eapsimlib.c
1 /*
2  * eapsimlib.c    based upon draft-haverinen-pppext-eap-sim-11.txt.
3  *
4  * The development of the EAP/SIM support was funded by Internet Foundation
5  * Austria (http://www.nic.at/ipa).
6  *
7  * code common to EAP-SIM clients and to servers.
8  *
9  * Version:     $Id$
10  *
11  *   This program is free software; you can redistribute it and/or modify
12  *   it under the terms of the GNU General Public License as published by
13  *   the Free Software Foundation; either version 2 of the License, or
14  *   (at your option) any later version.
15  *
16  *   This program is distributed in the hope that it will be useful,
17  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
18  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19  *   GNU General Public License for more details.
20  *
21  *   You should have received a copy of the GNU General Public License
22  *   along with this program; if not, write to the Free Software
23  *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
24  *
25  * Copyright 2000-2003,2006  The FreeRADIUS server project
26  * Copyright 2003  Michael Richardson <mcr@sandelman.ottawa.on.ca>
27  */
28
29 /*
30  *  EAP-SIM PACKET FORMAT
31  *  ------- ------ ------
32  *
33  * EAP Request and Response Packet Format
34  * --- ------- --- -------- ------ ------
35  *  0                   1                   2                   3
36  *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
37  * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
38  * |     Code      |  Identifier   |            Length             |
39  * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
40  * |     Type      |  SIM-Type     |   SIM-Length  |     value ... |
41  * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
42  *
43  * with SIM-Type/SIM-Length/Value... repeating. SIM-Length is in units
44  * of 32 bits, and includes the Sim-Type/Sim-Length fields.
45  *
46  * The SIM-Type's are mapped to ATTRIBUTE_EAP_SIM_BASE+Sim-type and
47  * unmapped by these functions.
48  *
49  */
50
51 #include <freeradius-devel/ident.h>
52 RCSID("$Id$")
53
54 #include <freeradius-devel/autoconf.h>
55 #include <freeradius-devel/missing.h>
56 #include <freeradius-devel/libradius.h>
57 #include "eap_types.h"
58 #include "eap_sim.h"
59 #include <freeradius-devel/sha1.h>
60
61 /*
62  * given a radius request with many attribues in the EAP-SIM range, build
63  * them all into a single EAP-SIM body.
64  *
65  */
66 int map_eapsim_basictypes(RADIUS_PACKET *r, EAP_PACKET *ep)
67 {
68         VALUE_PAIR       *vp;
69         int               encoded_size;
70         uint8_t          *encodedmsg, *attr;
71         unsigned int      id, eapcode;
72         unsigned char    *macspace, *append;
73         int               appendlen;
74         unsigned char     subtype;
75
76         macspace = NULL;
77         append = NULL;
78         appendlen = 0;
79
80         /*
81          * encodedmsg is now an EAP-SIM message.
82          * it might be too big for putting into an EAP-Type-SIM
83          *
84          */
85         vp = pairfind(r->vps, ATTRIBUTE_EAP_SIM_SUBTYPE);
86         if(vp == NULL)
87         {
88                 subtype = eapsim_start;
89         }
90         else
91         {
92                 subtype = vp->lvalue;
93         }
94
95         vp = pairfind(r->vps, ATTRIBUTE_EAP_ID);
96         if(vp == NULL)
97         {
98                 id = ((int)getpid() & 0xff);
99         }
100         else
101         {
102                 id = vp->lvalue;
103         }
104
105         vp = pairfind(r->vps, ATTRIBUTE_EAP_CODE);
106         if(vp == NULL)
107         {
108                 eapcode = PW_EAP_REQUEST;
109         }
110         else
111         {
112                 eapcode = vp->lvalue;
113         }
114
115
116         /*
117          * take a walk through the attribute list to see how much space
118          * that we need to encode all of this.
119          */
120         encoded_size = 0;
121         for(vp = r->vps; vp != NULL; vp = vp->next)
122         {
123                 int roundedlen;
124                 int vplen;
125
126                 if(vp->attribute < ATTRIBUTE_EAP_SIM_BASE ||
127                    vp->attribute >= ATTRIBUTE_EAP_SIM_BASE+256)
128                 {
129                         continue;
130                 }
131
132                 vplen = vp->length;
133
134                 /*
135                  * the AT_MAC attribute is a bit different, when we get to this
136                  * attribute, we pull the contents out, save it for later
137                  * processing, set the size to 16 bytes (plus 2 bytes padding).
138                  *
139                  * At this point, we only care about the size.
140                  */
141                 if(vp->attribute == ATTRIBUTE_EAP_SIM_BASE+PW_EAP_SIM_MAC) {
142                         vplen = 18;
143                 }
144
145                 /* round up to next multiple of 4, after taking in
146                  * account the type and length bytes
147                  */
148                 roundedlen = (vplen + 2 + 3) & ~3;
149                 encoded_size += roundedlen;
150         }
151
152         if (ep->code != PW_EAP_SUCCESS)
153                 ep->code = eapcode;
154         ep->id = (id & 0xff);
155         ep->type.type = PW_EAP_SIM;
156
157         /*
158          * if no attributes were found, do very little.
159          *
160          */
161         if(encoded_size == 0)
162         {
163                 encodedmsg = malloc(3);
164                 /* FIX: could be NULL */
165
166                 encodedmsg[0]=subtype;
167                 encodedmsg[1]=0;
168                 encodedmsg[2]=0;
169
170                 ep->type.length = 3;
171                 ep->type.data = encodedmsg;
172
173                 return 0;
174         }
175
176
177         /*
178          * figured out the length, so malloc some space for the results.
179          *
180          * Note that we do not bother going through an "EAP" stage, which
181          * is a bit strange compared to the unmap, which expects to see
182          * an EAP-SIM virtual attributes.
183          *
184          * EAP is 1-code, 1-identifier, 2-length, 1-type = 5 overhead.
185          *
186          * SIM code adds a subtype, and 2 bytes of reserved = 3.
187          *
188          */
189
190         /* malloc space for it */
191
192         encoded_size += 3;
193         encodedmsg = malloc(encoded_size);
194         if (encodedmsg == NULL) {
195                 radlog(L_ERR, "eapsim: out of memory allocating %d bytes", encoded_size+5);
196                 return 0;
197         }
198         memset(encodedmsg, 0, encoded_size);
199
200         /*
201          * now walk the attributes again, sticking them in.
202          *
203          * we go three bytes into the encoded message, because there are two
204          * bytes of reserved, and we will fill the "subtype" in later.
205          *
206          */
207         attr = encodedmsg+3;
208
209         for(vp = r->vps; vp != NULL; vp = vp->next)
210         {
211                 int roundedlen;
212
213                 if(vp->attribute < ATTRIBUTE_EAP_SIM_BASE ||
214                    vp->attribute >= ATTRIBUTE_EAP_SIM_BASE+256)
215                 {
216                         continue;
217                 }
218
219                 /*
220                  * the AT_MAC attribute is a bit different, when we get to this
221                  * attribute, we pull the contents out, save it for later
222                  * processing, set the size to 16 bytes (plus 2 bytes padding).
223                  *
224                  * At this point, we put in zeros, and remember where the
225                  * sixteen bytes go.
226                  */
227                 if(vp->attribute == ATTRIBUTE_EAP_SIM_BASE+PW_EAP_SIM_MAC) {
228                         roundedlen = 20;
229                         memset(&attr[2], 0, 18);
230                         macspace = &attr[4];
231                         append = vp->vp_octets;
232                         appendlen = vp->length;
233                 }
234                 else {
235                         roundedlen = (vp->length + 2 + 3) & ~3;
236                         memset(attr, 0, roundedlen);
237                         memcpy(&attr[2], vp->vp_strvalue, vp->length);
238                 }
239                 attr[0] = vp->attribute - ATTRIBUTE_EAP_SIM_BASE;
240                 attr[1] = roundedlen >> 2;
241
242                 attr += roundedlen;
243         }
244
245         encodedmsg[0] = subtype;
246
247         ep->type.length = encoded_size;
248         ep->type.data = encodedmsg;
249
250         /*
251          * if macspace was set and we have a key,
252          * then we should calculate the HMAC-SHA1 of the resulting EAP-SIM
253          * packet, appended with the value of append.
254          */
255         vp = pairfind(r->vps, ATTRIBUTE_EAP_SIM_KEY);
256         if(macspace != NULL && vp != NULL)
257         {
258                 unsigned char   *buffer;
259                 eap_packet_t    *hdr;
260                 uint16_t         hmaclen, total_length = 0;
261                 unsigned char    sha1digest[20];
262
263                 total_length = EAP_HEADER_LEN + 1 + encoded_size;
264                 hmaclen = total_length + appendlen;
265                 buffer = (unsigned char *)malloc(hmaclen);
266                 hdr = (eap_packet_t *)buffer;
267                 if (!hdr) {
268                         radlog(L_ERR, "rlm_eap: out of memory");
269                         free(encodedmsg);
270                         return 0;
271                 }
272
273                 hdr->code = eapcode & 0xFF;
274                 hdr->id = (id & 0xFF);
275                 total_length = htons(total_length);
276                 memcpy(hdr->length, &total_length, sizeof(uint16_t));
277
278                 hdr->data[0] = PW_EAP_SIM;
279
280                 /* copy the data */
281                 memcpy(&hdr->data[1], encodedmsg, encoded_size);
282
283                 /* copy the nonce */
284                 memcpy(&hdr->data[encoded_size+1], append, appendlen);
285
286                 /* HMAC it! */
287                 lrad_hmac_sha1(buffer, hmaclen,
288                                vp->vp_octets, vp->length,
289                                sha1digest);
290
291                 /* done with the buffer, free it */
292                 free(buffer);
293
294                 /* now copy the digest to where it belongs in the AT_MAC */
295                 /* note that it is truncated to 128-bits */
296                 memcpy(macspace, sha1digest, 16);
297         }
298
299         /* if we had an AT_MAC and no key, then fail */
300         if(macspace != NULL && vp == NULL)
301         {
302                 if(encodedmsg != NULL)
303                         free(encodedmsg);
304                 return 0;
305         }
306
307         return 1;
308 }
309
310 int map_eapsim_types(RADIUS_PACKET *r)
311 {
312         EAP_PACKET ep;
313         int ret;
314
315         memset(&ep, 0, sizeof(ep));
316         ret = map_eapsim_basictypes(r, &ep);
317         if(ret != 1) {
318                 return ret;
319         }
320         eap_basic_compose(r, &ep);
321
322         return 1;
323 }
324
325 /*
326  * given a radius request with an EAP-SIM body, decode it into TLV pairs
327  *
328  * return value is TRUE if it succeeded, false if there was something
329  * wrong and the packet should be discarded.
330  *
331  */
332 int unmap_eapsim_basictypes(RADIUS_PACKET *r,
333                             uint8_t *attr, unsigned int attrlen)
334 {
335         VALUE_PAIR              *newvp;
336         int                     eapsim_attribute;
337         unsigned int            eapsim_len;
338         int                     es_attribute_count;
339
340         es_attribute_count=0;
341
342         /* big enough to have even a single attribute */
343         if(attrlen < 5) {
344                 radlog(L_ERR, "eap: EAP-Sim attribute too short: %d < 2", attrlen);
345                 return 0;
346         }
347
348         newvp = paircreate(ATTRIBUTE_EAP_SIM_SUBTYPE, PW_TYPE_INTEGER);
349         if (!newvp) return 0;
350         newvp->lvalue = attr[0];
351         newvp->length = 1;
352         pairadd(&(r->vps), newvp);
353
354         attr     += 3;
355         attrlen  -= 3;
356
357         /* now, loop processing each attribute that we find */
358         while(attrlen > 0)
359         {
360                 if(attrlen < 2) {
361                         radlog(L_ERR, "eap: EAP-Sim attribute %d too short: %d < 2", es_attribute_count, attrlen);
362                         return 0;
363                 }
364
365                 eapsim_attribute = attr[0];
366                 eapsim_len = attr[1] * 4;
367
368                 if(eapsim_len > attrlen) {
369                         radlog(L_ERR, "eap: EAP-Sim attribute %d (no.%d) has length longer than data (%d > %d)"
370                                , eapsim_attribute
371                                , es_attribute_count, eapsim_len, attrlen);
372                         return 0;
373                 }
374
375                 if(eapsim_len > MAX_STRING_LEN) {
376                         eapsim_len = MAX_STRING_LEN;
377                 }
378                 if (eapsim_len < 2) {
379                         radlog(L_ERR, "eap: EAP-Sim attribute %d (no.%d) has length too small",
380                                eapsim_attribute, es_attribute_count);
381                                return 0;
382                 }
383
384                 newvp = paircreate(eapsim_attribute+ATTRIBUTE_EAP_SIM_BASE, PW_TYPE_OCTETS);
385                 memcpy(newvp->vp_strvalue, &attr[2], eapsim_len-2);
386                 newvp->length = eapsim_len-2;
387                 pairadd(&(r->vps), newvp);
388                 newvp = NULL;
389
390                 /* advance pointers, decrement length */
391                 attr += eapsim_len;
392                 attrlen  -= eapsim_len;
393                 es_attribute_count++;
394         }
395         return 1;
396 }
397
398 int unmap_eapsim_types(RADIUS_PACKET *r)
399 {
400         VALUE_PAIR             *esvp;
401
402         esvp = pairfind(r->vps, ATTRIBUTE_EAP_BASE+PW_EAP_SIM);
403         if (esvp == NULL) {
404                 radlog(L_ERR, "eap: EAP-Sim attribute not found");
405                 return 0;
406         }
407
408         return unmap_eapsim_basictypes(r, esvp->vp_octets, esvp->length);
409 }
410
411 /*
412  * calculate the MAC for the EAP message, given the key.
413  * The "extra" will be appended to the EAP message and included in the
414  * HMAC.
415  *
416  */
417 int
418 eapsim_checkmac(VALUE_PAIR *rvps,
419                 uint8_t key[EAPSIM_AUTH_SIZE],
420                 uint8_t *extra, int extralen,
421                 uint8_t calcmac[20])
422 {
423         int ret;
424         eap_packet_t *e;
425         uint8_t *buffer;
426         int elen,len;
427         VALUE_PAIR *mac;
428
429         mac = pairfind(rvps, ATTRIBUTE_EAP_SIM_BASE+PW_EAP_SIM_MAC);
430
431         if(mac == NULL
432            || mac->length != 18) {
433                 /* can't check a packet with no AT_MAC attribute */
434                 return 0;
435         }
436
437         /* get original copy of EAP message, note that it was sanitized
438          * to have a valid length, which we depend upon.
439          */
440         e = eap_attribute(rvps);
441
442         if(e == NULL)
443         {
444                 return 0;
445         }
446
447         /* make copy big enough for everything */
448         elen = e->length[0]*256 + e->length[1];
449         len = elen + extralen;
450
451         buffer = malloc(len);
452         if(buffer == NULL)
453         {
454                 free(e);
455                 return 0;
456         }
457
458         memcpy(buffer, e, elen);
459         memcpy(buffer+elen, extra, extralen);
460
461         /*
462          * now look for the AT_MAC attribute in the copy of the buffer
463          * and make sure that the checksum is zero.
464          *
465          */
466         {
467                 uint8_t *attr;
468
469                 /* first attribute is 8 bytes into the EAP packet.
470                  * 4 bytes for EAP, 1 for type, 1 for subtype, 2 reserved.
471                  */
472                 attr = buffer+8;
473                 while(attr < (buffer+elen)) {
474                         if(attr[0] == PW_EAP_SIM_MAC) {
475                                 /* zero the data portion, after making sure
476                                  * the size is >=5. Maybe future versions.
477                                  * will use more bytes, so be liberal.
478                                  */
479                                 if(attr[1] < 5) {
480                                         ret = 0;
481                                         goto done;
482                                 }
483                                 memset(&attr[4], 0, (attr[1]-1)*4);
484                         }
485                         /* advance the pointer */
486                         attr += attr[1]*4;
487                 }
488         }
489
490         /* now, HMAC-SHA1 it with the key. */
491         lrad_hmac_sha1(buffer, len,
492                        key, 16,
493                        calcmac);
494
495         if(memcmp(&mac->vp_strvalue[2], calcmac, 16) == 0)      {
496                 ret = 1;
497         } else {
498                 ret = 0;
499         }
500
501  done:
502         free(e);
503         free(buffer);
504         return(ret);
505 }
506
507 /*
508  * definitions changed to take a buffer for unknowns
509  * as this is more thread safe.
510  */
511 const char *simstates[]={ "init", "start", NULL };
512
513 const char *sim_state2name(enum eapsim_clientstates state,
514                            char *statenamebuf,
515                            int   statenamebuflen)
516 {
517         if(state >= eapsim_client_maxstates)
518         {
519                 snprintf(statenamebuf, statenamebuflen,
520                          "eapstate:%d", state);
521                 return statenamebuf;
522         }
523         else
524         {
525                 return simstates[state];
526         }
527 }
528
529 const char *subtypes[]={ "subtype0", "subtype1", "subtype2", "subtype3",
530                          "subtype4", "subtype5", "subtype6", "subtype7",
531                          "subtype8", "subtype9",
532                          "start",
533                          "challenge",
534                          "notification",
535                          "reauth",
536                          "client-error",
537                          NULL };
538
539 const char *sim_subtype2name(enum eapsim_subtype subtype,
540                              char *subtypenamebuf,
541                              int   subtypenamebuflen)
542 {
543         if(subtype >= eapsim_max_subtype)
544         {
545                 snprintf(subtypenamebuf, subtypenamebuflen,
546                          "illegal-subtype:%d", subtype);
547                 return subtypenamebuf;
548         }
549         else
550         {
551                 return subtypes[subtype];
552         }
553 }
554
555
556
557 #ifdef TEST_CASE
558
559 #include <assert.h>
560
561 const char *radius_dir = RADDBDIR;
562
563 int radlog(int lvl, const char *msg, ...)
564 {
565         va_list ap;
566         int r;
567
568         va_start(ap, msg);
569         r = vfprintf(stderr, msg, ap);
570         va_end(ap);
571         fputc('\n', stderr);
572
573         return r;
574 }
575
576 main(int argc, char *argv[])
577 {
578         int filedone;
579         RADIUS_PACKET *req,*req2;
580         VALUE_PAIR *vp, *vpkey, *vpextra;
581         extern unsigned int sha1_data_problems;
582
583         req = NULL;
584         req2 = NULL;
585         filedone = 0;
586
587         if(argc>1) {
588           sha1_data_problems = 1;
589         }
590
591         if (dict_init(radius_dir, RADIUS_DICTIONARY) < 0) {
592                 librad_perror("radclient");
593                 return 1;
594         }
595
596         if ((req = rad_alloc(1)) == NULL) {
597                 librad_perror("radclient");
598                 exit(1);
599         }
600
601         if ((req2 = rad_alloc(1)) == NULL) {
602                 librad_perror("radclient");
603                 exit(1);
604         }
605
606         while(!filedone) {
607                 if(req->vps) pairfree(&req->vps);
608                 if(req2->vps) pairfree(&req2->vps);
609
610                 if ((req->vps = readvp2(stdin, &filedone, "eapsimlib:")) == NULL) {
611                         break;
612                 }
613
614                 printf("\nRead:\n");
615                 vp_printlist(stdout, req->vps);
616
617                 map_eapsim_types(req);
618                 map_eap_types(req);
619                 printf("Mapped to:\n");
620                 vp_printlist(stdout, req->vps);
621
622                 /* find the EAP-Message, copy it to req2 */
623                 vp = paircopy2(req->vps, PW_EAP_MESSAGE);
624
625                 if(vp == NULL) continue;
626
627                 pairadd(&req2->vps, vp);
628
629                 /* only call unmap for sim types here */
630                 unmap_eap_types(req2);
631                 unmap_eapsim_types(req2);
632
633                 printf("Unmapped to:\n");
634                 vp_printlist(stdout, req2->vps);
635
636                 vp = pairfind(req2->vps,
637                               ATTRIBUTE_EAP_SIM_BASE+PW_EAP_SIM_MAC);
638                 vpkey   = pairfind(req->vps, ATTRIBUTE_EAP_SIM_KEY);
639                 vpextra = pairfind(req->vps, ATTRIBUTE_EAP_SIM_EXTRA);
640
641                 if(vp != NULL && vpkey != NULL && vpextra!=NULL) {
642                         uint8_t calcmac[16];
643
644                         /* find the EAP-Message, copy it to req2 */
645
646                         memset(calcmac, 0, sizeof(calcmac));
647                         printf("Confirming MAC...");
648                         if(eapsim_checkmac(req2->vps, vpkey->vp_strvalue,
649                                            vpextra->vp_strvalue, vpextra->length,
650                                            calcmac)) {
651                                 printf("succeed\n");
652                         } else {
653                                 int i, j;
654
655                                 printf("calculated MAC (");
656                                 for (i = 0; i < 20; i++) {
657                                         if(j==4) {
658                                                 printf("_");
659                                                 j=0;
660                                         }
661                                         j++;
662
663                                         printf("%02x", calcmac[i]);
664                                 }
665                                 printf(" did not match\n");
666                         }
667                 }
668
669                 fflush(stdout);
670         }
671 }
672 #endif
673
674
675