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         newvp->lvalue = attr[0];
347         newvp->length = 1;
348         pairadd(&(r->vps), newvp);
349
350         attr     += 3;
351         attrlen  -= 3;
352
353         /* now, loop processing each attribute that we find */
354         while(attrlen > 0)
355         {
356                 if(attrlen < 2) {
357                         radlog(L_ERR, "eap: EAP-Sim attribute %d too short: %d < 2", es_attribute_count, attrlen);
358                         return 0;
359                 }
360
361                 eapsim_attribute = attr[0];
362                 eapsim_len = attr[1] * 4;
363
364                 if(eapsim_len > attrlen) {
365                         radlog(L_ERR, "eap: EAP-Sim attribute %d (no.%d) has length longer than data (%d > %d)"
366                                , eapsim_attribute
367                                , es_attribute_count, eapsim_len, attrlen);
368                         return 0;
369                 }
370
371                 if(eapsim_len > MAX_STRING_LEN) {
372                         eapsim_len = MAX_STRING_LEN;
373                 }
374                 if (eapsim_len < 2) {
375                         radlog(L_ERR, "eap: EAP-Sim attribute %d (no.%d) has length too small",
376                                eapsim_attribute, es_attribute_count);
377                                return 0;
378                 }
379
380                 newvp = paircreate(eapsim_attribute+ATTRIBUTE_EAP_SIM_BASE, PW_TYPE_OCTETS);
381                 memcpy(newvp->strvalue, &attr[2], eapsim_len-2);
382                 newvp->length = eapsim_len-2;
383                 pairadd(&(r->vps), newvp);
384                 newvp = NULL;
385
386                 /* advance pointers, decrement length */
387                 attr += eapsim_len;
388                 attrlen  -= eapsim_len;
389                 es_attribute_count++;
390         }
391         return 1;
392 }
393
394 int unmap_eapsim_types(RADIUS_PACKET *r)
395 {
396         VALUE_PAIR             *esvp;
397
398         esvp = pairfind(r->vps, ATTRIBUTE_EAP_BASE+PW_EAP_SIM);
399         if (esvp == NULL) {
400                 radlog(L_ERR, "eap: EAP-Sim attribute not found");
401                 return 0;
402         }
403
404         return unmap_eapsim_basictypes(r, esvp->strvalue, esvp->length);
405 }
406
407 /*
408  * calculate the MAC for the EAP message, given the key.
409  * The "extra" will be appended to the EAP message and included in the
410  * HMAC.
411  *
412  */
413 int
414 eapsim_checkmac(VALUE_PAIR *rvps,
415                 uint8_t key[EAPSIM_AUTH_SIZE],
416                 uint8_t *extra, int extralen,
417                 uint8_t calcmac[20])
418 {
419         int ret;
420         eap_packet_t *e;
421         uint8_t *buffer;
422         int elen,len;
423         VALUE_PAIR *mac;
424
425         mac = pairfind(rvps, ATTRIBUTE_EAP_SIM_BASE+PW_EAP_SIM_MAC);
426
427         if(mac == NULL
428            || mac->length != 18) {
429                 /* can't check a packet with no AT_MAC attribute */
430                 return 0;
431         }
432
433         /* get original copy of EAP message, note that it was sanitized
434          * to have a valid length, which we depend upon.
435          */
436         e = eap_attribute(rvps);
437
438         if(e == NULL)
439         {
440                 return 0;
441         }
442
443         /* make copy big enough for everything */
444         elen = e->length[0]*256 + e->length[1];
445         len = elen + extralen;
446
447         buffer = malloc(len);
448         if(buffer == NULL)
449         {
450                 free(e);
451                 return 0;
452         }
453
454         memcpy(buffer, e, elen);
455         memcpy(buffer+elen, extra, extralen);
456
457         /*
458          * now look for the AT_MAC attribute in the copy of the buffer
459          * and make sure that the checksum is zero.
460          *
461          */
462         {
463                 uint8_t *attr;
464
465                 /* first attribute is 8 bytes into the EAP packet.
466                  * 4 bytes for EAP, 1 for type, 1 for subtype, 2 reserved.
467                  */
468                 attr = buffer+8;
469                 while(attr < (buffer+elen)) {
470                         if(attr[0] == PW_EAP_SIM_MAC) {
471                                 /* zero the data portion, after making sure
472                                  * the size is >=5. Maybe future versions.
473                                  * will use more bytes, so be liberal.
474                                  */
475                                 if(attr[1] < 5) {
476                                         ret = 0;
477                                         goto done;
478                                 }
479                                 memset(&attr[4], 0, (attr[1]-1)*4);
480                         }
481                         /* advance the pointer */
482                         attr += attr[1]*4;
483                 }
484         }
485
486         /* now, HMAC-SHA1 it with the key. */
487         lrad_hmac_sha1(buffer, len,
488                        key, 16,
489                        calcmac);
490
491         if(memcmp(&mac->strvalue[2], calcmac, 16) == 0) {
492                 ret = 1;
493         } else {
494                 ret = 0;
495         }
496
497  done:
498         free(e);
499         free(buffer);
500         return(ret);
501 }
502
503 /*
504  * definitions changed to take a buffer for unknowns
505  * as this is more thread safe.
506  */
507 const char *simstates[]={ "init", "start", NULL };
508
509 const char *sim_state2name(enum eapsim_clientstates state,
510                            char *statenamebuf,
511                            int   statenamebuflen)
512 {
513         if(state >= eapsim_client_maxstates)
514         {
515                 snprintf(statenamebuf, statenamebuflen,
516                          "eapstate:%d", state);
517                 return statenamebuf;
518         }
519         else
520         {
521                 return simstates[state];
522         }
523 }
524
525 const char *subtypes[]={ "subtype0", "subtype1", "subtype2", "subtype3",
526                          "subtype4", "subtype5", "subtype6", "subtype7",
527                          "subtype8", "subtype9",
528                          "start",
529                          "challenge",
530                          "notification",
531                          "reauth",
532                          NULL };
533
534 const char *sim_subtype2name(enum eapsim_subtype subtype,
535                              char *subtypenamebuf,
536                              int   subtypenamebuflen)
537 {
538         if(subtype >= eapsim_max_subtype)
539         {
540                 snprintf(subtypenamebuf, subtypenamebuflen,
541                          "illegal-subtype:%d", subtype);
542                 return subtypenamebuf;
543         }
544         else
545         {
546                 return subtypes[subtype];
547         }
548 }
549
550
551
552 #ifdef TEST_CASE
553
554 #include <assert.h>
555
556 const char *radius_dir = RADDBDIR;
557
558 int radlog(int lvl, const char *msg, ...)
559 {
560         va_list ap;
561         int r;
562
563         va_start(ap, msg);
564         r = vfprintf(stderr, msg, ap);
565         va_end(ap);
566         fputc('\n', stderr);
567
568         return r;
569 }
570
571 main(int argc, char *argv[])
572 {
573         int filedone;
574         RADIUS_PACKET *req,*req2;
575         VALUE_PAIR *vp, *vpkey, *vpextra;
576         extern unsigned int sha1_data_problems;
577
578         req = NULL;
579         req2 = NULL;
580         filedone = 0;
581
582         if(argc>1) {
583           sha1_data_problems = 1;
584         }
585
586         if (dict_init(radius_dir, RADIUS_DICTIONARY) < 0) {
587                 librad_perror("radclient");
588                 return 1;
589         }
590
591         if ((req = rad_alloc(1)) == NULL) {
592                 librad_perror("radclient");
593                 exit(1);
594         }
595
596         if ((req2 = rad_alloc(1)) == NULL) {
597                 librad_perror("radclient");
598                 exit(1);
599         }
600
601         while(!filedone) {
602                 if(req->vps) pairfree(&req->vps);
603                 if(req2->vps) pairfree(&req2->vps);
604
605                 if ((req->vps = readvp2(stdin, &filedone, "eapsimlib:")) == NULL) {
606                         break;
607                 }
608
609                 printf("\nRead:\n");
610                 vp_printlist(stdout, req->vps);
611
612                 map_eapsim_types(req);
613                 map_eap_types(req);
614                 printf("Mapped to:\n");
615                 vp_printlist(stdout, req->vps);
616
617                 /* find the EAP-Message, copy it to req2 */
618                 vp = paircopy2(req->vps, PW_EAP_MESSAGE);
619
620                 if(vp == NULL) continue;
621
622                 pairadd(&req2->vps, vp);
623
624                 /* only call unmap for sim types here */
625                 unmap_eap_types(req2);
626                 unmap_eapsim_types(req2);
627
628                 printf("Unmapped to:\n");
629                 vp_printlist(stdout, req2->vps);
630
631                 vp = pairfind(req2->vps,
632                               ATTRIBUTE_EAP_SIM_BASE+PW_EAP_SIM_MAC);
633                 vpkey   = pairfind(req->vps, ATTRIBUTE_EAP_SIM_KEY);
634                 vpextra = pairfind(req->vps, ATTRIBUTE_EAP_SIM_EXTRA);
635
636                 if(vp != NULL && vpkey != NULL && vpextra!=NULL) {
637                         uint8_t calcmac[16];
638
639                         /* find the EAP-Message, copy it to req2 */
640
641                         memset(calcmac, 0, sizeof(calcmac));
642                         printf("Confirming MAC...");
643                         if(eapsim_checkmac(req2->vps, vpkey->strvalue,
644                                            vpextra->strvalue, vpextra->length,
645                                            calcmac)) {
646                                 printf("succeed\n");
647                         } else {
648                                 int i, j;
649
650                                 printf("calculated MAC (");
651                                 for (i = 0; i < 20; i++) {
652                                         if(j==4) {
653                                                 printf("_");
654                                                 j=0;
655                                         }
656                                         j++;
657
658                                         printf("%02x", calcmac[i]);
659                                 }
660                                 printf(" did not match\n");
661                         }
662                 }
663
664                 fflush(stdout);
665         }
666 }
667 #endif
668
669
670