/*
* state.c To generate and verify State attribute
*
* $Id: state.c,v 1.10 2004/03/19 02:22:16 mcr 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 2001 hereUare Communications, Inc. <raghud@hereuare.com>
*/
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include "rlm_eap.h"
static const char rcsid[] = "$Id: state.c,v 1.10 2004/03/19 02:22:16 mcr Exp $";
/*
* Global key to generate & verify State
*
* This is needed only once per instance of the server,
* and putting it in the rlm_eap_t is just too much effort.
*
* Putting it here is ugly, but it works.
*/
static int key_initialized = 0;
static unsigned char state_key[AUTH_VECTOR_LEN];
/*
* Generate & Verify the State attribute
*
* In the simplest implementation, we would just use the
* challenge as state. Unfortunately, the RADIUS secret protects
* only the User-Password attribute; an attacker that can remove
* packets from the wire and insert new ones can simply insert a
* replayed state without having to know the secret.
*
* However, RADIUS packets containing EAP conversations MUST be
* signed with Message-Authenticator, at which point, they MUST
* know the secret, in order to get to the EAP module. And if
* they know the secret, they can do many worse things than
* re-playing a State attribute. Their only alternative is to
* re-play entire packets, which is caught by the server core.
*
* In any case, we sign our state with data unique to this
* specific request. A NAS would use the Request Authenticator,
* we don't know what that will be when the State is returned to
* us, so we'll use a time stamp.
*
* Our replay prevention is limited to a time interval
* (inst->maxdelay). We could keep track of all challenges
* issued over that time interval, to ensure that the challenges
* were unique. However, they're 8-bytes of data from a good
* PRNG, which means that it's pretty damn unlikely that they'll
* be re-used.
*
* Our state, then, is (challenge + hmac(challenge + time, key)),
* where '+' denotes concatentation, 'challenge' is the octets
* of the challenge, 'time' is the 'time_t' in host byte order,
* and 'key' is a random key, generated once in eap_init().
*
* This means that only the server which generates a challenge
* can verify it; this should be OK if your NAS's load balance
* across RADIUS servers by a "first available" algorithm. If
* your NAS's round-robin (ugh), you could use the RADIUS
* secret instead, but read RFC 2104 first, and make very sure
* you really want to do this.
*/
void generate_key(void)
{
unsigned int i;
if (key_initialized) return;
/*
* Use a cryptographically strong method to generate
* pseudo-random numbers.
*/
for (i = 0; i < sizeof(state_key); i++) {
state_key[i] = lrad_rand();
}
key_initialized = 1;
}
/*
* For clarity. Also, to avoid giving away
* too much information, we only put 8 octets of the HMAC
* into the State attribute, instead of all 16.
*
* As a security feature, it's a little hokey, but WTF.
*
* Also, ensure that EAP_CHALLENGE_LEN + EAP_USE_OF_HMAC = EAP_STATE_LEN
*/
#define EAP_CHALLENGE_LEN (8)
#define EAP_HMAC_SIZE (16)
#define EAP_USE_OF_HMAC (8)
/*
* Our state, is (challenge + time + hmac(challenge + time, key))
*
* If it's too long, then some clients chop it (sigh)
*/
VALUE_PAIR *generate_state(time_t timestamp)
{
unsigned int i;
unsigned char challenge[EAP_CHALLENGE_LEN];
unsigned char hmac[EAP_HMAC_SIZE];
unsigned char value[EAP_CHALLENGE_LEN + sizeof(timestamp)];
VALUE_PAIR *state;
/* Generate challenge (a random value). */
for (i = 0; i < sizeof(challenge); i++) {
challenge[i] = lrad_rand();
}
memcpy(value, challenge, sizeof(challenge));
memcpy(value + sizeof(challenge), ×tamp, sizeof(timestamp));
/*
* hmac(challenge + timestamp)
*/
lrad_hmac_md5(value, sizeof(value),
state_key, sizeof(state_key), hmac);
/*
* Create the state attribute.
*
* Note that the timestamp is used internally, but is NOT
* sent to the client!
*/
state = paircreate(PW_STATE, PW_TYPE_OCTETS);
if (state == NULL) {
radlog(L_ERR, "rlm_eap: out of memory");
return NULL;
}
memcpy(state->strvalue, challenge, sizeof(challenge));
memcpy(state->strvalue + sizeof(challenge), hmac,
EAP_USE_OF_HMAC);
state->length = sizeof(challenge) + EAP_USE_OF_HMAC;
return state;
}
/*
* Returns 0 on success, non-zero otherwise.
*/
int verify_state(VALUE_PAIR *state, time_t timestamp)
{
unsigned char hmac[EAP_HMAC_SIZE];
unsigned char value[EAP_CHALLENGE_LEN + sizeof(timestamp)];
/*
* The length is wrong. Don't do anything.
*/
if (state->length != EAP_STATE_LEN) {
return -1;
}
/*
* The first 16 octets of the State attribute constains
* the random challenge.
*/
memcpy(value, state->strvalue, EAP_CHALLENGE_LEN);
memcpy(value + EAP_CHALLENGE_LEN, ×tamp, sizeof(timestamp));
/* Generate hmac. */
lrad_hmac_md5(value, sizeof(value),
state_key, sizeof(state_key), hmac);
/*
* Compare the hmac we calculated to the one in the
* packet.
*/
return memcmp(hmac, state->strvalue + EAP_CHALLENGE_LEN,
EAP_USE_OF_HMAC);
}
syntax highlighted by Code2HTML, v. 0.9.1