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