generate State as ASCII to workaround Cisco bug
authorfcusack <fcusack>
Wed, 8 Feb 2006 21:11:24 +0000 (21:11 +0000)
committerfcusack <fcusack>
Wed, 8 Feb 2006 21:11:24 +0000 (21:11 +0000)
src/modules/rlm_otp/otp_radstate.c
src/modules/rlm_otp/otp_rlm.c

index 3202ebd..f218133 100644 (file)
@@ -17,7 +17,7 @@
  *   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
@@ -37,15 +37,13 @@ static const char rcsid[] = "$Id$";
 
 /*
  * 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
@@ -64,24 +62,29 @@ static const char rcsid[] = "$Id$";
  *
  * 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])
@@ -89,6 +92,7 @@ otp_gen_state(char **ascii_state, unsigned char **raw_state,
   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
@@ -102,49 +106,48 @@ otp_gen_state(char **ascii_state, unsigned char **raw_state,
   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;
 }
index 596d332..23a8b71 100644 (file)
@@ -50,8 +50,8 @@
 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[] = {
@@ -473,15 +473,16 @@ otp_authenticate(void *instance, REQUEST *request)
   {
     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;
 
@@ -492,16 +493,32 @@ otp_authenticate(void *instance, REQUEST *request)
       }
 
       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);