#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.
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.
*
* 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
* 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;
/*
* 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;
/*
* 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;
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;
}
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");
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;
.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,
},
}
/*
- * TOTP <time> <key> <8-character-expected-token>
+ * TOTP <time> <key> <expected-token>
*/
if (strcmp(argv[1], "totp") == 0) {
uint64_t now;
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");