]> git.ipfire.org Git - thirdparty/freeradius-server.git/commitdiff
Add config variables for lookback code, and make TIME_STEP and OTP sixe configurable.
authorMargaret Cullen <margaret@painless-security.com>
Tue, 15 Aug 2023 19:03:47 +0000 (15:03 -0400)
committerAlan T. DeKok <aland@freeradius.org>
Wed, 16 Aug 2023 12:45:53 +0000 (08:45 -0400)
Signed-off-by: Margaret Cullen <margaret@painless-security.com>
src/modules/rlm_totp/rlm_totp.c

index 32328b0fcc825e371b5e8a78aa6fb8a6a3a585f3..68d666d8771f4a21786294419f80f156a71034a4 100644 (file)
@@ -28,9 +28,37 @@ RCSID("$Id$")
 #include <freeradius-devel/modules.h>
 #include <freeradius-devel/rad_assert.h>
 
-#define TIME_STEP (30)
-#define BACK_STEPS (1)
-#define BACK_STEP_SECS (30)
+#include <ctype.h>
+
+/* Define a structure for the configuration variables */
+typedef struct rlm_totp_t {
+        char const     *name;                  //!< name of this instance */
+        uint32_t       time_step;              //!< seconds
+        uint32_t       otp_length;             //!< forced to 6 or 8
+        uint32_t       lookback_steps;         //!< number of steps to look back
+        uint32_t       lookback_interval;      //!< interval in seconds between steps
+} rlm_totp_t;
+
+#ifndef TESTING
+/* Map configuration file names to internal variables */
+static const CONF_PARSER module_config[] = {
+        { "time_step", FR_CONF_OFFSET(PW_TYPE_INTEGER, rlm_totp_t, time_step), "30" },
+        { "otp_length", FR_CONF_OFFSET(PW_TYPE_INTEGER, rlm_totp_t, otp_length), "6" },
+       { "lookback_steps", FR_CONF_OFFSET(PW_TYPE_INTEGER, rlm_totp_t, lookback_steps), "1" },
+       { "lookback_interval", FR_CONF_OFFSET(PW_TYPE_INTEGER, rlm_totp_t, lookback_interval), "30" },
+       CONF_PARSER_TERMINATOR
+};
+
+#define TIME_STEP      (inst->time_step)
+#define OTP_LEN        (inst->otp_length)
+#define BACK_STEPS     (inst->lookback_steps)
+#define BACK_STEP_SECS (inst->lookback_interval)
+#else
+#define TIME_STEP      (30)
+#define OTP_LEN                (8)
+#define BACK_STEPS     (1)
+#define BACK_STEP_SECS (30)
+#endif
 
 /*
  *     RFC 4648 base32 decoding.
@@ -144,23 +172,60 @@ static ssize_t base32_decode(uint8_t *out, size_t outlen, char const *in)
        return b - out;
 }
 
+
 #ifndef TESTING
 #define TESTING_UNUSED
-#define LEN 6
-#define PRINT "%06u"
-#define DIV 1000000
-
-#else
-#define LEN 8
-#define PRINT "%08u"
-#define DIV 100000000
-
 
+#else /* TESTING */
 #undef RDEBUG3
 #define RDEBUG3(fmt, ...)      printf(fmt "\n", ## __VA_ARGS__)
 #define TESTING_UNUSED UNUSED
 #endif
 
+#ifndef TESTING
+static int mod_bootstrap(CONF_SECTION *conf, void *instance)
+{
+        rlm_totp_t *inst = instance;
+
+        inst->name = cf_section_name2(conf);
+        if (!inst->name) {
+                inst->name = cf_section_name1(conf);
+        }
+
+        return 0;
+}
+
+/*
+ *     Do any per-module initialization that is separate to each
+ *     configured instance of the module.  e.g. set up connections
+ *     to external databases, read configuration files, set up
+ *     dictionary entries, etc.
+ *
+ *     If configuration information is given in the config section
+ *     that must be referenced in later calls, store a handle to it
+ *     in *instance otherwise put a null pointer there.
+ */
+static int mod_instantiate(UNUSED CONF_SECTION *conf, void *instance)
+{
+       rlm_totp_t *inst = instance;
+
+       FR_INTEGER_BOUND_CHECK("time_step", inst->time_step, >=, 5);
+       FR_INTEGER_BOUND_CHECK("time_step", inst->time_step, <=, 120);
+
+       FR_INTEGER_BOUND_CHECK("lookback_steps", inst->lookback_steps, >=, 1);
+       FR_INTEGER_BOUND_CHECK("lookback_steps", inst->lookback_steps, <=, 10);
+
+       FR_INTEGER_BOUND_CHECK("lookback_interval", inst->lookback_interval, <=, inst->time_step);
+
+       FR_INTEGER_BOUND_CHECK("otp_length", inst->otp_length, >=, 6);
+       FR_INTEGER_BOUND_CHECK("otp_length", inst->otp_length, <=, 8);
+
+       if (inst->otp_length == 7) inst->otp_length = 8;
+
+       return 0;
+}
+#endif
+
 /*
  *     Implement RFC 6238 TOTP algorithm.
  *
@@ -168,16 +233,19 @@ static ssize_t base32_decode(uint8_t *out, size_t outlen, char const *in)
  *     for 8-character challenges, and not for 6 character
  *     challenges!
  */
-static int totp_cmp(TESTING_UNUSED REQUEST *request, time_t now, uint8_t const *key, size_t keylen, char const *totp)
+static int totp_cmp(TESTING_UNUSED REQUEST *request, time_t now, uint8_t const *key, size_t keylen, char const *totp, TESTING_UNUSED void *instance)
 {
+#ifndef TESTING
+        rlm_totp_t *inst = instance;
+#endif
+       time_t then;
+       unsigned int i;
        uint8_t offset;
        uint32_t challenge;
        uint64_t padded;
        char buffer[9];
        uint8_t data[8];
        uint8_t digest[SHA1_DIGEST_LENGTH];
-       time_t then;
-       int i;
 
        /*
         *      First try to authenticate against the current OTP, then step
@@ -185,6 +253,7 @@ static int totp_cmp(TESTING_UNUSED REQUEST *request, time_t now, uint8_t const *
         *      to authenticate properly in cases of long transit delay, as
         *      described in RFC 6238, secion 5.2.
         */
+
        for (i = 0, then = now; i <= BACK_STEPS; i++, then -= BACK_STEP_SECS) {
               padded = (uint64_t) then / TIME_STEP;
               data[0] = padded >> 56;
@@ -217,13 +286,14 @@ static int totp_cmp(TESTING_UNUSED REQUEST *request, time_t now, uint8_t const *
               /*
                *       The token is the last 6 digits in the number (or 8 for testing)..
                */
-              snprintf(buffer, sizeof(buffer), PRINT, challenge % DIV);
+               snprintf(buffer, sizeof(buffer), ((OTP_LEN == 6) ? "%06u" : "%08u"),
+                       challenge % ((OTP_LEN == 6) ? 1000000 : 100000000));
 
               RDEBUG3("Now: %zu, Then: %zu", (size_t) now, (size_t) then);
               RDEBUG3("Expected %s", buffer);
               RDEBUG3("Received %s", totp);
 
-              if (rad_digest_cmp((uint8_t const *) buffer, (uint8_t const *) totp, LEN) == 0)
+              if (rad_digest_cmp((uint8_t const *) buffer, (uint8_t const *) totp, OTP_LEN) == 0)
                      return 0;
        }
        return 1;
@@ -234,7 +304,7 @@ static int totp_cmp(TESTING_UNUSED REQUEST *request, time_t now, uint8_t const *
 /*
  *  Do the authentication
  */
-static rlm_rcode_t CC_HINT(nonnull) mod_authenticate(UNUSED void *instance, REQUEST *request)
+static rlm_rcode_t CC_HINT(nonnull) mod_authenticate(void *instance, REQUEST *request)
 {
        VALUE_PAIR *vp, *password;
        uint8_t const *key;
@@ -245,8 +315,8 @@ static rlm_rcode_t CC_HINT(nonnull) mod_authenticate(UNUSED void *instance, REQU
        password = fr_pair_find_by_num(request->packet->vps, PW_TOTP_PASSWORD, 0, TAG_ANY);
        if (!password) return RLM_MODULE_NOOP;
 
-       if (password->vp_length != 6) {
-               REDEBUG("TOTP-Password has incorrect length %d", (int) password->vp_length);
+       if ((password->vp_length != 6) && (password->vp_length != 8)) {
+               RDEBUG("TOTP-Password has incorrect length %d", (int) password->vp_length);
                return RLM_MODULE_FAIL;
        }
 
@@ -262,8 +332,10 @@ static rlm_rcode_t CC_HINT(nonnull) mod_authenticate(UNUSED void *instance, REQU
                ssize_t len;
 
                vp = fr_pair_find_by_num(request->config, PW_TOTP_SECRET, 0, TAG_ANY);
-               if (!vp) return RLM_MODULE_NOOP;
-
+               if (!vp) {
+                       RDEBUG("TOTP mod_authenticate() did not receive a TOTP-Secret");
+                       return RLM_MODULE_NOOP;
+               }
                len = base32_decode(buffer, sizeof(buffer), vp->vp_strvalue);
                if (len < 0) {
                        REDEBUG("TOTP-Secret cannot be decoded");
@@ -274,7 +346,7 @@ static rlm_rcode_t CC_HINT(nonnull) mod_authenticate(UNUSED void *instance, REQU
                keylen = len;
        }
 
-       if (totp_cmp(request, now, key, keylen, password->vp_strvalue) == 0)
+       if (totp_cmp(request, now, key, keylen, password->vp_strvalue, instance) == 0) {
                     return RLM_MODULE_OK;
        }
        return RLM_MODULE_FAIL;
@@ -295,6 +367,10 @@ module_t rlm_totp = {
        .magic          = RLM_MODULE_INIT,
        .name           = "totp",
        .type           = RLM_TYPE_THREAD_SAFE,
+       .inst_size      = sizeof(rlm_totp_t),
+       .config         = module_config,
+       .bootstrap      = mod_bootstrap,
+       .instantiate    = mod_instantiate,
        .methods = {
                [MOD_AUTHENTICATE]      = mod_authenticate,
        },
@@ -329,7 +405,7 @@ int main(int argc, char **argv)
        }
 
        /*
-        *      TOTP <time> <key> <8-character-expected-token>
+        *      TOTP <time> <key> <expected-token>
         */
        if (strcmp(argv[1], "totp") == 0) {
                uint64_t now;
@@ -346,7 +422,7 @@ int main(int argc, char **argv)
                         now, TIME_STEP, BACK_STEPS, BACK_STEP_SECS);
 
                if (totp_cmp(NULL, (time_t) now, (uint8_t const *) argv[3],
-                            strlen(argv[3]), argv[4]) == 0) {
+                            strlen(argv[3]), argv[4], NULL) == 0) {
                       return 0;
                }
                printf("Fail\n");