29b7866d6b97c670420fc381691a9b53adcee331
[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/libradius.h>
55 #include "eap_types.h"
56 #include "eap_sim.h"
57 #include <freeradius-devel/sha1.h>
58
59 /*
60  * given a radius request with many attribues in the EAP-SIM range, build
61  * them all into a single EAP-SIM body.
62  *
63  */
64 int map_eapsim_basictypes(RADIUS_PACKET *r, EAP_PACKET *ep)
65 {
66         VALUE_PAIR       *vp;
67         int               encoded_size;
68         uint8_t          *encodedmsg, *attr;
69         unsigned int      id, eapcode;
70         unsigned char    *macspace, *append;
71         int               appendlen;
72         unsigned char     subtype;
73
74         macspace = NULL;
75         append = NULL;
76         appendlen = 0;
77
78         /*
79          * encodedmsg is now an EAP-SIM message.
80          * it might be too big for putting into an EAP-Type-SIM
81          *
82          */
83         vp = pairfind(r->vps, ATTRIBUTE_EAP_SIM_SUBTYPE, 0);
84         if(vp == NULL)
85         {
86                 subtype = eapsim_start;
87         }
88         else
89         {
90                 subtype = vp->vp_integer;
91         }
92
93         vp = pairfind(r->vps, ATTRIBUTE_EAP_ID, 0);
94         if(vp == NULL)
95         {
96                 id = ((int)getpid() & 0xff);
97         }
98         else
99         {
100                 id = vp->vp_integer;
101         }
102
103         vp = pairfind(r->vps, ATTRIBUTE_EAP_CODE, 0);
104         if(vp == NULL)
105         {
106                 eapcode = PW_EAP_REQUEST;
107         }
108         else
109         {
110                 eapcode = vp->vp_integer;
111         }
112
113
114         /*
115          * take a walk through the attribute list to see how much space
116          * that we need to encode all of this.
117          */
118         encoded_size = 0;
119         for(vp = r->vps; vp != NULL; vp = vp->next)
120         {
121                 int roundedlen;
122                 int vplen;
123
124                 if(vp->attribute < ATTRIBUTE_EAP_SIM_BASE ||
125                    vp->attribute >= ATTRIBUTE_EAP_SIM_BASE+256)
126                 {
127                         continue;
128                 }
129
130                 vplen = vp->length;
131
132                 /*
133                  * the AT_MAC attribute is a bit different, when we get to this
134                  * attribute, we pull the contents out, save it for later
135                  * processing, set the size to 16 bytes (plus 2 bytes padding).
136                  *
137                  * At this point, we only care about the size.
138                  */
139                 if(vp->attribute == ATTRIBUTE_EAP_SIM_BASE+PW_EAP_SIM_MAC) {
140                         vplen = 18;
141                 }
142
143                 /* round up to next multiple of 4, after taking in
144                  * account the type and length bytes
145                  */
146                 roundedlen = (vplen + 2 + 3) & ~3;
147                 encoded_size += roundedlen;
148         }
149
150         if (ep->code != PW_EAP_SUCCESS)
151                 ep->code = eapcode;
152         ep->id = (id & 0xff);
153         ep->type.type = PW_EAP_SIM;
154
155         /*
156          * if no attributes were found, do very little.
157          *
158          */
159         if(encoded_size == 0)
160         {
161                 encodedmsg = malloc(3);
162                 /* FIX: could be NULL */
163
164                 encodedmsg[0]=subtype;
165                 encodedmsg[1]=0;
166                 encodedmsg[2]=0;
167
168                 ep->type.length = 3;
169                 ep->type.data = encodedmsg;
170
171                 return 0;
172         }
173
174
175         /*
176          * figured out the length, so malloc some space for the results.
177          *
178          * Note that we do not bother going through an "EAP" stage, which
179          * is a bit strange compared to the unmap, which expects to see
180          * an EAP-SIM virtual attributes.
181          *
182          * EAP is 1-code, 1-identifier, 2-length, 1-type = 5 overhead.
183          *
184          * SIM code adds a subtype, and 2 bytes of reserved = 3.
185          *
186          */
187
188         /* malloc space for it */
189
190         encoded_size += 3;
191         encodedmsg = malloc(encoded_size);
192         if (encodedmsg == NULL) {
193                 radlog(L_ERR, "eapsim: out of memory allocating %d bytes", encoded_size+5);
194                 return 0;
195         }
196         memset(encodedmsg, 0, encoded_size);
197
198         /*
199          * now walk the attributes again, sticking them in.
200          *
201          * we go three bytes into the encoded message, because there are two
202          * bytes of reserved, and we will fill the "subtype" in later.
203          *
204          */
205         attr = encodedmsg+3;
206
207         for(vp = r->vps; vp != NULL; vp = vp->next)
208         {
209                 int roundedlen;
210
211                 if(vp->attribute < ATTRIBUTE_EAP_SIM_BASE ||
212                    vp->attribute >= ATTRIBUTE_EAP_SIM_BASE+256)
213                 {
214                         continue;
215                 }
216
217                 /*
218                  * the AT_MAC attribute is a bit different, when we get to this
219                  * attribute, we pull the contents out, save it for later
220                  * processing, set the size to 16 bytes (plus 2 bytes padding).
221                  *
222                  * At this point, we put in zeros, and remember where the
223                  * sixteen bytes go.
224                  */
225                 if(vp->attribute == ATTRIBUTE_EAP_SIM_BASE+PW_EAP_SIM_MAC) {
226                         roundedlen = 20;
227                         memset(&attr[2], 0, 18);
228                         macspace = &attr[4];
229                         append = vp->vp_octets;
230                         appendlen = vp->length;
231                 }
232                 else {
233                         roundedlen = (vp->length + 2 + 3) & ~3;
234                         memset(attr, 0, roundedlen);
235                         memcpy(&attr[2], vp->vp_strvalue, vp->length);
236                 }
237                 attr[0] = vp->attribute - ATTRIBUTE_EAP_SIM_BASE;
238                 attr[1] = roundedlen >> 2;
239
240                 attr += roundedlen;
241         }
242
243         encodedmsg[0] = subtype;
244
245         ep->type.length = encoded_size;
246         ep->type.data = encodedmsg;
247
248         /*
249          * if macspace was set and we have a key,
250          * then we should calculate the HMAC-SHA1 of the resulting EAP-SIM
251          * packet, appended with the value of append.
252          */
253         vp = pairfind(r->vps, ATTRIBUTE_EAP_SIM_KEY, 0);
254         if(macspace != NULL && vp != NULL)
255         {
256                 unsigned char   *buffer;
257                 eap_packet_t    *hdr;
258                 uint16_t         hmaclen, total_length = 0;
259                 unsigned char    sha1digest[20];
260
261                 total_length = EAP_HEADER_LEN + 1 + encoded_size;
262                 hmaclen = total_length + appendlen;
263                 buffer = (unsigned char *)malloc(hmaclen);
264                 hdr = (eap_packet_t *)buffer;
265                 if (!hdr) {
266                         radlog(L_ERR, "rlm_eap: out of memory");
267                         free(encodedmsg);
268                         return 0;
269                 }
270
271                 hdr->code = eapcode & 0xFF;
272                 hdr->id = (id & 0xFF);
273                 total_length = htons(total_length);
274                 memcpy(hdr->length, &total_length, sizeof(total_length));
275
276                 hdr->data[0] = PW_EAP_SIM;
277
278                 /* copy the data */
279                 memcpy(&hdr->data[1], encodedmsg, encoded_size);
280
281                 /* copy the nonce */
282                 memcpy(&hdr->data[encoded_size+1], append, appendlen);
283
284                 /* HMAC it! */
285                 fr_hmac_sha1(buffer, hmaclen,
286                                vp->vp_octets, vp->length,
287                                sha1digest);
288
289                 /* done with the buffer, free it */
290                 free(buffer);
291
292                 /* now copy the digest to where it belongs in the AT_MAC */
293                 /* note that it is truncated to 128-bits */
294                 memcpy(macspace, sha1digest, 16);
295         }
296
297         /* if we had an AT_MAC and no key, then fail */
298         if(macspace != NULL && vp == NULL)
299         {
300                 if(encodedmsg != NULL)
301                         free(encodedmsg);
302                 return 0;
303         }
304
305         return 1;
306 }
307
308 /*
309  * given a radius request with an EAP-SIM body, decode it into TLV pairs
310  *
311  * return value is TRUE if it succeeded, false if there was something
312  * wrong and the packet should be discarded.
313  *
314  */
315 int unmap_eapsim_basictypes(RADIUS_PACKET *r,
316                             uint8_t *attr, unsigned int attrlen)
317 {
318         VALUE_PAIR              *newvp;
319         int                     eapsim_attribute;
320         unsigned int            eapsim_len;
321         int                     es_attribute_count;
322
323         es_attribute_count=0;
324
325         /* big enough to have even a single attribute */
326         if(attrlen < 5) {
327                 radlog(L_ERR, "eap: EAP-Sim attribute too short: %d < 2", attrlen);
328                 return 0;
329         }
330
331         newvp = paircreate(ATTRIBUTE_EAP_SIM_SUBTYPE, 0, PW_TYPE_INTEGER);
332         if (!newvp) return 0;
333         newvp->vp_integer = attr[0];
334         newvp->length = 1;
335         pairadd(&(r->vps), newvp);
336
337         attr     += 3;
338         attrlen  -= 3;
339
340         /* now, loop processing each attribute that we find */
341         while(attrlen > 0)
342         {
343                 if(attrlen < 2) {
344                         radlog(L_ERR, "eap: EAP-Sim attribute %d too short: %d < 2", es_attribute_count, attrlen);
345                         return 0;
346                 }
347
348                 eapsim_attribute = attr[0];
349                 eapsim_len = attr[1] * 4;
350
351                 if(eapsim_len > attrlen) {
352                         radlog(L_ERR, "eap: EAP-Sim attribute %d (no.%d) has length longer than data (%d > %d)"
353                                , eapsim_attribute
354                                , es_attribute_count, eapsim_len, attrlen);
355                         return 0;
356                 }
357
358                 if(eapsim_len > MAX_STRING_LEN) {
359                         eapsim_len = MAX_STRING_LEN;
360                 }
361                 if (eapsim_len < 2) {
362                         radlog(L_ERR, "eap: EAP-Sim attribute %d (no.%d) has length too small",
363                                eapsim_attribute, es_attribute_count);
364                                return 0;
365                 }
366
367                 newvp = paircreate(eapsim_attribute+ATTRIBUTE_EAP_SIM_BASE, 0, PW_TYPE_OCTETS);
368                 memcpy(newvp->vp_strvalue, &attr[2], eapsim_len-2);
369                 newvp->length = eapsim_len-2;
370                 pairadd(&(r->vps), newvp);
371                 newvp = NULL;
372
373                 /* advance pointers, decrement length */
374                 attr += eapsim_len;
375                 attrlen  -= eapsim_len;
376                 es_attribute_count++;
377         }
378         return 1;
379 }
380
381 /*
382  * calculate the MAC for the EAP message, given the key.
383  * The "extra" will be appended to the EAP message and included in the
384  * HMAC.
385  *
386  */
387 int
388 eapsim_checkmac(VALUE_PAIR *rvps,
389                 uint8_t key[EAPSIM_AUTH_SIZE],
390                 uint8_t *extra, int extralen,
391                 uint8_t calcmac[20])
392 {
393         int ret;
394         eap_packet_t *e;
395         uint8_t *buffer;
396         int elen,len;
397         VALUE_PAIR *mac;
398
399         mac = pairfind(rvps, ATTRIBUTE_EAP_SIM_BASE+PW_EAP_SIM_MAC, 0);
400
401         if(mac == NULL
402            || mac->length != 18) {
403                 /* can't check a packet with no AT_MAC attribute */
404                 return 0;
405         }
406
407         /* get original copy of EAP message, note that it was sanitized
408          * to have a valid length, which we depend upon.
409          */
410         e = eap_vp2packet(rvps);
411
412         if(e == NULL)
413         {
414                 return 0;
415         }
416
417         /* make copy big enough for everything */
418         elen = e->length[0] * 256 + e->length[1];
419         len = elen + extralen;
420
421         buffer = malloc(len);
422         if(buffer == NULL)
423         {
424                 free(e);
425                 return 0;
426         }
427
428         memcpy(buffer, e, elen);
429         memcpy(buffer+elen, extra, extralen);
430
431         /*
432          * now look for the AT_MAC attribute in the copy of the buffer
433          * and make sure that the checksum is zero.
434          *
435          */
436         {
437                 uint8_t *attr;
438
439                 /* first attribute is 8 bytes into the EAP packet.
440                  * 4 bytes for EAP, 1 for type, 1 for subtype, 2 reserved.
441                  */
442                 attr = buffer+8;
443                 while(attr < (buffer+elen)) {
444                         if(attr[0] == PW_EAP_SIM_MAC) {
445                                 /* zero the data portion, after making sure
446                                  * the size is >=5. Maybe future versions.
447                                  * will use more bytes, so be liberal.
448                                  */
449                                 if(attr[1] < 5) {
450                                         ret = 0;
451                                         goto done;
452                                 }
453                                 memset(&attr[4], 0, (attr[1]-1)*4);
454                         }
455                         /* advance the pointer */
456                         attr += attr[1]*4;
457                 }
458         }
459
460         /* now, HMAC-SHA1 it with the key. */
461         fr_hmac_sha1(buffer, len,
462                        key, 16,
463                        calcmac);
464
465         if(memcmp(&mac->vp_strvalue[2], calcmac, 16) == 0)      {
466                 ret = 1;
467         } else {
468                 ret = 0;
469         }
470
471  done:
472         free(e);
473         free(buffer);
474         return(ret);
475 }
476
477 /*
478  * definitions changed to take a buffer for unknowns
479  * as this is more thread safe.
480  */
481 const char *simstates[]={ "init", "start", NULL };
482
483 const char *sim_state2name(enum eapsim_clientstates state,
484                            char *statenamebuf,
485                            int   statenamebuflen)
486 {
487         if(state >= eapsim_client_maxstates)
488         {
489                 snprintf(statenamebuf, statenamebuflen,
490                          "eapstate:%d", state);
491                 return statenamebuf;
492         }
493         else
494         {
495                 return simstates[state];
496         }
497 }
498
499 const char *subtypes[]={ "subtype0", "subtype1", "subtype2", "subtype3",
500                          "subtype4", "subtype5", "subtype6", "subtype7",
501                          "subtype8", "subtype9",
502                          "start",
503                          "challenge",
504                          "notification",
505                          "reauth",
506                          "client-error",
507                          NULL };
508
509 const char *sim_subtype2name(enum eapsim_subtype subtype,
510                              char *subtypenamebuf,
511                              int   subtypenamebuflen)
512 {
513         if(subtype >= eapsim_max_subtype)
514         {
515                 snprintf(subtypenamebuf, subtypenamebuflen,
516                          "illegal-subtype:%d", subtype);
517                 return subtypenamebuf;
518         }
519         else
520         {
521                 return subtypes[subtype];
522         }
523 }