/*
* eapsimlib.c based upon draft-haverinen-pppext-eap-sim-11.txt.
*
* The development of the EAP/SIM support was funded by Internet Foundation
* Austria (http://www.nic.at/ipa).
*
* code common to EAP-SIM clients and to servers.
*
* Version: $Id: eapsimlib.c,v 1.7.2.2.2.1 2007/02/15 12:51:37 aland Exp $
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* Copyright 2000-2003 The FreeRADIUS server project
* Copyright 2003 Michael Richardson <mcr@sandelman.ottawa.on.ca>
*/
/*
* EAP-SIM PACKET FORMAT
* ------- ------ ------
*
* EAP Request and Response Packet Format
* --- ------- --- -------- ------ ------
* 0 1 2 3
* 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
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Code | Identifier | Length |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Type | SIM-Type | SIM-Length | value ... |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*
* with SIM-Type/SIM-Length/Value... repeating. SIM-Length is in units
* of 32 bits, and includes the Sim-Type/Sim-Length fields.
*
* The SIM-Type's are mapped to ATTRIBUTE_EAP_SIM_BASE+Sim-type and
* unmapped by these functions.
*
*/
#include "libradius.h"
#include "eap_types.h"
#include "eap_sim.h"
#include "sha1.h"
static const char rcsid[] = "$Id: eapsimlib.c,v 1.7.2.2.2.1 2007/02/15 12:51:37 aland Exp $";
/*
* given a radius request with many attribues in the EAP-SIM range, build
* them all into a single EAP-SIM body.
*
*/
int map_eapsim_basictypes(RADIUS_PACKET *r, EAP_PACKET *ep)
{
VALUE_PAIR *vp;
int encoded_size;
uint8_t *encodedmsg, *attr;
unsigned int id, eapcode;
unsigned char *macspace, *append;
int appendlen;
unsigned char subtype;
macspace = NULL;
append = NULL;
appendlen = 0;
/*
* encodedmsg is now an EAP-SIM message.
* it might be too big for putting into an EAP-Type-SIM
*
*/
vp = pairfind(r->vps, ATTRIBUTE_EAP_SIM_SUBTYPE);
if(vp == NULL)
{
subtype = eapsim_start;
}
else
{
subtype = vp->lvalue;
}
vp = pairfind(r->vps, ATTRIBUTE_EAP_ID);
if(vp == NULL)
{
id = ((int)getpid() & 0xff);
}
else
{
id = vp->lvalue;
}
vp = pairfind(r->vps, ATTRIBUTE_EAP_CODE);
if(vp == NULL)
{
eapcode = PW_EAP_REQUEST;
}
else
{
eapcode = vp->lvalue;
}
/*
* take a walk through the attribute list to see how much space
* that we need to encode all of this.
*/
encoded_size = 0;
for(vp = r->vps; vp != NULL; vp = vp->next)
{
int roundedlen;
int vplen;
if(vp->attribute < ATTRIBUTE_EAP_SIM_BASE ||
vp->attribute >= ATTRIBUTE_EAP_SIM_BASE+256)
{
continue;
}
vplen = vp->length;
/*
* the AT_MAC attribute is a bit different, when we get to this
* attribute, we pull the contents out, save it for later
* processing, set the size to 16 bytes (plus 2 bytes padding).
*
* At this point, we only care about the size.
*/
if(vp->attribute == ATTRIBUTE_EAP_SIM_BASE+PW_EAP_SIM_MAC) {
vplen = 18;
}
/* round up to next multiple of 4, after taking in
* account the type and length bytes
*/
roundedlen = (vplen + 2 + 3) & ~3;
encoded_size += roundedlen;
}
if (ep->code != PW_EAP_SUCCESS)
ep->code = eapcode;
ep->id = (id & 0xff);
ep->type.type = PW_EAP_SIM;
/*
* if no attributes were found, do very little.
*
*/
if(encoded_size == 0)
{
encodedmsg = malloc(3);
/* FIX: could be NULL */
encodedmsg[0]=subtype;
encodedmsg[1]=0;
encodedmsg[2]=0;
ep->type.length = 3;
ep->type.data = encodedmsg;
return 0;
}
/*
* figured out the length, so malloc some space for the results.
*
* Note that we do not bother going through an "EAP" stage, which
* is a bit strange compared to the unmap, which expects to see
* an EAP-SIM virtual attributes.
*
* EAP is 1-code, 1-identifier, 2-length, 1-type = 5 overhead.
*
* SIM code adds a subtype, and 2 bytes of reserved = 3.
*
*/
/* malloc space for it */
encoded_size += 3;
encodedmsg = malloc(encoded_size);
if (encodedmsg == NULL) {
radlog(L_ERR, "eapsim: out of memory allocating %d bytes", encoded_size+5);
return 0;
}
memset(encodedmsg, 0, encoded_size);
/*
* now walk the attributes again, sticking them in.
*
* we go three bytes into the encoded message, because there are two
* bytes of reserved, and we will fill the "subtype" in later.
*
*/
attr = encodedmsg+3;
for(vp = r->vps; vp != NULL; vp = vp->next)
{
int roundedlen;
if(vp->attribute < ATTRIBUTE_EAP_SIM_BASE ||
vp->attribute >= ATTRIBUTE_EAP_SIM_BASE+256)
{
continue;
}
/*
* the AT_MAC attribute is a bit different, when we get to this
* attribute, we pull the contents out, save it for later
* processing, set the size to 16 bytes (plus 2 bytes padding).
*
* At this point, we put in zeros, and remember where the
* sixteen bytes go.
*/
if(vp->attribute == ATTRIBUTE_EAP_SIM_BASE+PW_EAP_SIM_MAC) {
roundedlen = 20;
memset(&attr[2], 0, 18);
macspace = &attr[4];
append = vp->strvalue;
appendlen = vp->length;
}
else {
roundedlen = (vp->length + 2 + 3) & ~3;
memset(attr, 0, roundedlen);
memcpy(&attr[2], vp->strvalue, vp->length);
}
attr[0] = vp->attribute - ATTRIBUTE_EAP_SIM_BASE;
attr[1] = roundedlen >> 2;
attr += roundedlen;
}
encodedmsg[0] = subtype;
ep->type.length = encoded_size;
ep->type.data = encodedmsg;
/*
* if macspace was set and we have a key,
* then we should calculate the HMAC-SHA1 of the resulting EAP-SIM
* packet, appended with the value of append.
*/
vp = pairfind(r->vps, ATTRIBUTE_EAP_SIM_KEY);
if(macspace != NULL && vp != NULL)
{
unsigned char *buffer;
eap_packet_t *hdr;
uint16_t hmaclen, total_length = 0;
unsigned char sha1digest[20];
total_length = EAP_HEADER_LEN + 1 + encoded_size;
hmaclen = total_length + appendlen;
buffer = (unsigned char *)malloc(hmaclen);
hdr = (eap_packet_t *)buffer;
if (!hdr) {
radlog(L_ERR, "rlm_eap: out of memory");
free(encodedmsg);
return 0;
}
hdr->code = eapcode & 0xFF;
hdr->id = (id & 0xFF);
total_length = htons(total_length);
memcpy(hdr->length, &total_length, sizeof(uint16_t));
hdr->data[0] = PW_EAP_SIM;
/* copy the data */
memcpy(&hdr->data[1], encodedmsg, encoded_size);
/* copy the nonce */
memcpy(&hdr->data[encoded_size+1], append, appendlen);
/* HMAC it! */
lrad_hmac_sha1(buffer, hmaclen,
vp->strvalue, vp->length,
sha1digest);
/* done with the buffer, free it */
free(buffer);
/* now copy the digest to where it belongs in the AT_MAC */
/* note that it is truncated to 128-bits */
memcpy(macspace, sha1digest, 16);
}
/* if we had an AT_MAC and no key, then fail */
if(macspace != NULL && vp == NULL)
{
if(encodedmsg != NULL)
free(encodedmsg);
return 0;
}
return 1;
}
int map_eapsim_types(RADIUS_PACKET *r)
{
EAP_PACKET ep;
int ret;
memset(&ep, 0, sizeof(ep));
ret = map_eapsim_basictypes(r, &ep);
if(ret != 1) {
return ret;
}
eap_basic_compose(r, &ep);
return 1;
}
/*
* given a radius request with an EAP-SIM body, decode it into TLV pairs
*
* return value is TRUE if it succeeded, false if there was something
* wrong and the packet should be discarded.
*
*/
int unmap_eapsim_basictypes(RADIUS_PACKET *r,
uint8_t *attr, unsigned int attrlen)
{
VALUE_PAIR *newvp;
int eapsim_attribute;
unsigned int eapsim_len;
int es_attribute_count;
es_attribute_count=0;
/* big enough to have even a single attribute */
if(attrlen < 5) {
radlog(L_ERR, "eap: EAP-Sim attribute too short: %d < 2", attrlen);
return 0;
}
newvp = paircreate(ATTRIBUTE_EAP_SIM_SUBTYPE, PW_TYPE_INTEGER);
if (!newvp) return 0;
newvp->lvalue = attr[0];
newvp->length = 1;
pairadd(&(r->vps), newvp);
attr += 3;
attrlen -= 3;
/* now, loop processing each attribute that we find */
while(attrlen > 0)
{
if(attrlen < 2) {
radlog(L_ERR, "eap: EAP-Sim attribute %d too short: %d < 2", es_attribute_count, attrlen);
return 0;
}
eapsim_attribute = attr[0];
eapsim_len = attr[1] * 4;
if(eapsim_len > attrlen) {
radlog(L_ERR, "eap: EAP-Sim attribute %d (no.%d) has length longer than data (%d > %d)"
, eapsim_attribute
, es_attribute_count, eapsim_len, attrlen);
return 0;
}
if(eapsim_len > MAX_STRING_LEN) {
eapsim_len = MAX_STRING_LEN;
}
if (eapsim_len < 2) {
radlog(L_ERR, "eap: EAP-Sim attribute %d (no.%d) has length too small",
eapsim_attribute, es_attribute_count);
return 0;
}
newvp = paircreate(eapsim_attribute+ATTRIBUTE_EAP_SIM_BASE, PW_TYPE_OCTETS);
memcpy(newvp->strvalue, &attr[2], eapsim_len-2);
newvp->length = eapsim_len-2;
pairadd(&(r->vps), newvp);
newvp = NULL;
/* advance pointers, decrement length */
attr += eapsim_len;
attrlen -= eapsim_len;
es_attribute_count++;
}
return 1;
}
int unmap_eapsim_types(RADIUS_PACKET *r)
{
VALUE_PAIR *esvp;
esvp = pairfind(r->vps, ATTRIBUTE_EAP_BASE+PW_EAP_SIM);
if (esvp == NULL) {
radlog(L_ERR, "eap: EAP-Sim attribute not found");
return 0;
}
return unmap_eapsim_basictypes(r, esvp->strvalue, esvp->length);
}
/*
* calculate the MAC for the EAP message, given the key.
* The "extra" will be appended to the EAP message and included in the
* HMAC.
*
*/
int
eapsim_checkmac(VALUE_PAIR *rvps,
uint8_t key[EAPSIM_AUTH_SIZE],
uint8_t *extra, int extralen,
uint8_t calcmac[20])
{
int ret;
eap_packet_t *e;
uint8_t *buffer;
int elen,len;
VALUE_PAIR *mac;
mac = pairfind(rvps, ATTRIBUTE_EAP_SIM_BASE+PW_EAP_SIM_MAC);
if(mac == NULL
|| mac->length != 18) {
/* can't check a packet with no AT_MAC attribute */
return 0;
}
/* get original copy of EAP message, note that it was sanitized
* to have a valid length, which we depend upon.
*/
e = eap_attribute(rvps);
if(e == NULL)
{
return 0;
}
/* make copy big enough for everything */
elen = e->length[0]*256 + e->length[1];
len = elen + extralen;
buffer = malloc(len);
if(buffer == NULL)
{
free(e);
return 0;
}
memcpy(buffer, e, elen);
memcpy(buffer+elen, extra, extralen);
/*
* now look for the AT_MAC attribute in the copy of the buffer
* and make sure that the checksum is zero.
*
*/
{
uint8_t *attr;
/* first attribute is 8 bytes into the EAP packet.
* 4 bytes for EAP, 1 for type, 1 for subtype, 2 reserved.
*/
attr = buffer+8;
while(attr < (buffer+elen)) {
if(attr[0] == PW_EAP_SIM_MAC) {
/* zero the data portion, after making sure
* the size is >=5. Maybe future versions.
* will use more bytes, so be liberal.
*/
if(attr[1] < 5) {
ret = 0;
goto done;
}
memset(&attr[4], 0, (attr[1]-1)*4);
}
/* advance the pointer */
attr += attr[1]*4;
}
}
/* now, HMAC-SHA1 it with the key. */
lrad_hmac_sha1(buffer, len,
key, 16,
calcmac);
if(memcmp(&mac->strvalue[2], calcmac, 16) == 0) {
ret = 1;
} else {
ret = 0;
}
done:
free(e);
free(buffer);
return(ret);
}
/*
* definitions changed to take a buffer for unknowns
* as this is more thread safe.
*/
const char *simstates[]={ "init", "start", NULL };
const char *sim_state2name(enum eapsim_clientstates state,
char *statenamebuf,
int statenamebuflen)
{
if(state >= eapsim_client_maxstates)
{
snprintf(statenamebuf, statenamebuflen,
"eapstate:%d", state);
return statenamebuf;
}
else
{
return simstates[state];
}
}
const char *subtypes[]={ "subtype0", "subtype1", "subtype2", "subtype3",
"subtype4", "subtype5", "subtype6", "subtype7",
"subtype8", "subtype9",
"start",
"challenge",
"notification",
"reauth",
"client-error",
NULL };
const char *sim_subtype2name(enum eapsim_subtype subtype,
char *subtypenamebuf,
int subtypenamebuflen)
{
if(subtype >= eapsim_max_subtype)
{
snprintf(subtypenamebuf, subtypenamebuflen,
"illegal-subtype:%d", subtype);
return subtypenamebuf;
}
else
{
return subtypes[subtype];
}
}
#ifdef TEST_CASE
#include <assert.h>
const char *radius_dir = RADDBDIR;
int radlog(int lvl, const char *msg, ...)
{
va_list ap;
int r;
va_start(ap, msg);
r = vfprintf(stderr, msg, ap);
va_end(ap);
fputc('\n', stderr);
return r;
}
main(int argc, char *argv[])
{
int filedone;
RADIUS_PACKET *req,*req2;
VALUE_PAIR *vp, *vpkey, *vpextra;
extern unsigned int sha1_data_problems;
req = NULL;
req2 = NULL;
filedone = 0;
if(argc>1) {
sha1_data_problems = 1;
}
if (dict_init(radius_dir, RADIUS_DICTIONARY) < 0) {
librad_perror("radclient");
return 1;
}
if ((req = rad_alloc(1)) == NULL) {
librad_perror("radclient");
exit(1);
}
if ((req2 = rad_alloc(1)) == NULL) {
librad_perror("radclient");
exit(1);
}
while(!filedone) {
if(req->vps) pairfree(&req->vps);
if(req2->vps) pairfree(&req2->vps);
if ((req->vps = readvp2(stdin, &filedone, "eapsimlib:")) == NULL) {
break;
}
printf("\nRead:\n");
vp_printlist(stdout, req->vps);
map_eapsim_types(req);
map_eap_types(req);
printf("Mapped to:\n");
vp_printlist(stdout, req->vps);
/* find the EAP-Message, copy it to req2 */
vp = paircopy2(req->vps, PW_EAP_MESSAGE);
if(vp == NULL) continue;
pairadd(&req2->vps, vp);
/* only call unmap for sim types here */
unmap_eap_types(req2);
unmap_eapsim_types(req2);
printf("Unmapped to:\n");
vp_printlist(stdout, req2->vps);
vp = pairfind(req2->vps,
ATTRIBUTE_EAP_SIM_BASE+PW_EAP_SIM_MAC);
vpkey = pairfind(req->vps, ATTRIBUTE_EAP_SIM_KEY);
vpextra = pairfind(req->vps, ATTRIBUTE_EAP_SIM_EXTRA);
if(vp != NULL && vpkey != NULL && vpextra!=NULL) {
uint8_t calcmac[16];
/* find the EAP-Message, copy it to req2 */
memset(calcmac, 0, sizeof(calcmac));
printf("Confirming MAC...");
if(eapsim_checkmac(req2->vps, vpkey->strvalue,
vpextra->strvalue, vpextra->length,
calcmac)) {
printf("succeed\n");
} else {
int i, j;
printf("calculated MAC (");
for (i = 0; i < 20; i++) {
if(j==4) {
printf("_");
j=0;
}
j++;
printf("%02x", calcmac[i]);
}
printf(" did not match\n");
}
}
fflush(stdout);
}
}
#endif
syntax highlighted by Code2HTML, v. 0.9.1