* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* Copyright 2001,2002 Google, Inc.
- * Copyright 2005 TRI-D Systems, Inc.
+ * Copyright 2005,2006 TRI-D Systems, Inc.
*/
#ifdef FREERADIUS
/*
* Generate the State attribute, suitable for passing to pairmake().
- * challenge must be a null terminated string, and be sized at least
+ * 'challenge' must be a null terminated string, and be sized at least
* as large as indicated in the function definition.
*
* Returns 0 on success, non-zero otherwise. For successful returns,
- * ascii_state (suitable for passing to pairmake()) and raw_state, if
- * non-NULL, will be pointing to allocated storage. The caller is
- * responsible for freeing the storage. raw_state will not be
- * null-terminated, the caller should know the expected size (any
- * variance in size is solely due to the length of the challenge arg).
+ * 'rad_state' (suitable for passing to pairmake()) and 'raw_state',
+ * if non-NULL, will be pointing to allocated storage. The caller is
+ * responsible for freeing the storage.
*
* In the simplest implementation, we would just use the challenge as state.
* Unfortunately, the RADIUS secret protects only the User-Password
*
* Our state, then, is
* (challenge + flags + time + hmac(challenge + resync + time, key)),
- * where '+' denotes concatentation, 'challenge' is ...
- * the challenge, 'flags' is a 32-bit value that can be used to record
- * additional info, 'time' is the 32-bit time (LSB if time_t is 64 bits)
- * in network byte order, and 'key' is a random key, generated in
- * otp_instantiate(). 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 using 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.
+ * where '+' denotes concatentation, 'challenge' is ... the challenge,
+ * 'flags' is a 32-bit value that can be used to record additional info,
+ * 'time' is the 32-bit time (LSB if time_t is 64 bits), and 'key' is a
+ * random key, generated in otp_instantiate(). 'flags' and 'time' are
+ * in network byte order.
*
- * Note that putting the time in network byte order is pointless, since
- * only "this" server will be able to verify the hmac, due to the unique
- * key. But I've left it in there for future consideration of sync'd
- * keys across servers (eg, using the RADIUS secret, which is probably
- * not a good idea; or reading from a file, which might be OK.)
+ * As the signing key is unique to each server, only the server which
+ * generates a challenge can verify it; this should be OK if your NAS's
+ * load balance across RADIUS servers using a "first available" algorithm.
+ * If your NAS's round-robin and don't "stick" to the same server if they
+ * see a State attribute (ugh), you could use the RADIUS secret instead,
+ * but read RFC 2104 first, and make very sure you really want to do this.
+ *
+ * Since only the "same server" can verify State, 'flags' and 'time' doesn't
+ * really need to be in network byte order, but we do it anyway.
+ *
+ * The State attribute is an octet string, however some versions of Cisco
+ * IOS and Catalyst OS (at least IOS 12.1(26)E4 and CatOS 7.6.12) treat it
+ * as an ASCII string (they only return data up to the first NUL byte).
+ * So we must handle state as an ASCII string (0x00 -> 0x3030).
*/
int
-otp_gen_state(char **ascii_state, unsigned char **raw_state,
+otp_gen_state(char **rad_state, unsigned char **raw_state,
const unsigned char challenge[OTP_MAX_CHALLENGE_LEN],
size_t clen,
int32_t flags, int32_t when, const unsigned char key[16])
HMAC_CTX hmac_ctx;
unsigned char hmac[MD5_DIGEST_LENGTH];
char *p;
+ char *state;
/*
* Generate the hmac. We already have a dependency on openssl for
HMAC_Final(&hmac_ctx, hmac, NULL);
HMAC_cleanup(&hmac_ctx);
- /* Fill in raw_state if requested. */
- if (raw_state) {
- *raw_state = rad_malloc(clen + 8 + sizeof(hmac));
- p = *raw_state;
- (void) memcpy(p, challenge, clen);
- p += clen;
- (void) memcpy(p, &flags, 4);
- p += 4;
- (void) memcpy(p, &when, 4);
- p += 4;
- (void) memcpy(p, hmac, sizeof(hmac));
- }
-
/*
- * Fill in ascii_state if requested. (pairmake() forces us to to this.)
- * "0x" is required for pairmake(). Note that each octet expands into
- * 2 hex digits in ASCII (0xAA -> 0x4141).
+ * Generate the state. Note that it is in ASCII. The challenge
+ * value doesn't have to be ASCII encoded, as it is already
+ * ASCII, but we do it anyway, for consistency.
*/
- if (ascii_state) {
- *ascii_state = rad_malloc(2 + /* "0x" */
- clen * 2 + /* challenge */
- 8 + /* flags */
- 8 + /* time */
- sizeof(hmac) * 2 + /* hmac */
- 1); /* '\0' */
- (void) sprintf(*ascii_state, "0x");
- p = *ascii_state + 2;
-
- /* Add the challenge. */
- (void) otp_keyblock2keystring(p, challenge, clen, otp_hex_conversion);
- p += clen * 2;
+ state = rad_malloc(clen * 2 + /* challenge */
+ 8 + /* flags */
+ 8 + /* time */
+ sizeof(hmac) * 2 + /* hmac */
+ 1); /* '\0' */
+ p = state;
+ /* Add the challenge. */
+ (void) otp_keyblock2keystring(p, challenge, clen, otp_hex_conversion);
+ p += clen * 2;
+ /* Add the flags and time. */
+ (void) otp_keyblock2keystring(p, (unsigned char *) &flags, 4,
+ otp_hex_conversion);
+ p += 8;
+ (void) otp_keyblock2keystring(p, (unsigned char *) &when, 4,
+ otp_hex_conversion);
+ p += 8;
+ /* Add the hmac. */
+ (void) otp_keyblock2keystring(p, hmac, 16, otp_hex_conversion);
- /* Add the flags and time. */
- (void) otp_keyblock2keystring(p, (unsigned char *) &flags, 4,
- otp_hex_conversion);
- p += 8;
- (void) otp_keyblock2keystring(p, (unsigned char *) &when, 4,
- otp_hex_conversion);
- p += 8;
-
- /* Add the hmac. */
- (void) otp_keyblock2keystring(p, hmac, 16, otp_hex_conversion);
+ /*
+ * Expand state (already ASCII) into ASCII again (0x31 -> 0x3331).
+ * pairmake() forces us to do this (it will reduce it back to binary),
+ * and to include a leading "0x".
+ */
+ if (rad_state) {
+ *rad_state = rad_malloc(2 + /* "0x" */
+ strlen(state) * 2 +
+ 1); /* '\0' */
+ (void) sprintf(*rad_state, "0x");
+ p = *rad_state + 2;
+ (void) otp_keyblock2keystring(p, state, strlen(state), otp_hex_conversion);
}
+ if (raw_state)
+ *raw_state = state;
+ else
+ free(state);
+
return 0;
}
static const char rcsid[] = "$Id$";
/* Global data */
-static unsigned char hmac_key[16]; /* to protect State attribute */
-static int ninstance = 0; /* #instances, for global init */
+static unsigned char hmac_key[16]; /* to protect State attribute */
+static int ninstance = 0; /* #instances, for global init */
/* A mapping of configuration file names to internal variables. */
static const CONF_PARSER module_config[] = {
{
VALUE_PAIR *vp;
unsigned char *state;
- int32_t sflags = 0; /* state flags */
- int32_t then; /* state timestamp */
+ unsigned char *raw_state;
+ unsigned char *rad_state;
+ int32_t sflags = 0; /* state flags */
+ int32_t then; /* state timestamp */
+ int e_length; /* expected State length */
if ((vp = pairfind(request->packet->vps, PW_STATE)) != NULL) {
- int e_length;
-
/* set expected State length */
if (inst->allow_async)
- e_length += inst->chal_len + 4 + 4 + 16; /* see otp_gen_state() */
+ e_length += inst->chal_len * 2 + 8 + 8 + 32; /* see otp_gen_state() */
else
e_length = 1;
}
if (inst->allow_async) {
- /* Verify the state. */
- (void) memcpy(challenge, vp->vp_strvalue, inst->chal_len);
- (void) memcpy(&sflags, vp->vp_strvalue + inst->chal_len, 4);
- (void) memcpy(&then, vp->vp_strvalue + inst->chal_len + 4, 4);
+ /*
+ * Verify the state.
+ */
+
+ /* ASCII decode */
+ rad_state = rad_malloc(e_length + 1);
+ (void) memcpy(rad_state, vp->vp_strvalue, vp->length);
+ rad_state[e_length] = '\0';
+ (void) otp_keystring2keyblock(rad_state, raw_state);
+ free(rad_state);
+
+ /* extract data from State */
+ raw_state = rad_malloc(e_length / 2);
+ (void) memcpy(challenge, raw_state, inst->chal_len);
+ (void) memcpy(&sflags, raw_state + inst->chal_len, 4);
+ (void) memcpy(&then, raw_state + inst->chal_len + 4, 4);
+ free(raw_state);
+
+ /* generate new state from returned input data */
if (otp_gen_state(NULL, &state, challenge, inst->chal_len,
sflags, then, hmac_key) != 0) {
otp_log(OTP_LOG_ERR, "%s: %s: failed to generate state",
log_prefix, __func__);
return RLM_MODULE_FAIL;
}
+ /* compare generated state against returned state to verify hmac */
if (memcmp(state, vp->vp_strvalue, vp->length)) {
otp_log(OTP_LOG_AUTH, "%s: %s: bad state for [%s]: hmac",
log_prefix, __func__, username);