5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 * Copyright 2000,2001,2002 The FreeRADIUS server project
20 * Copyright 2001,2002 Google, Inc.
24 * STRONG WARNING SECTION:
26 * ANSI X9.9 has been withdrawn as a standard, due to the weakness of DES.
27 * An attacker can learn the token's secret by observing two
28 * challenge/response pairs. See ANSI document X9 TG-24-1999
29 * <URL:http://www.x9.org/TG24_1999.pdf>.
31 * Please read the accompanying docs.
35 * TODO: support soft PIN? ???
36 * TODO: support other than ILP32 (for State)
42 #include <sys/types.h>
47 #include <netinet/in.h> /* htonl() */
55 static const char rcsid[] = "$Id$";
58 static int rnd_fd; /* fd for random device */
59 static unsigned char hmac_key[16]; /* to protect State attribute */
61 /* A mapping of configuration file names to internal variables. */
62 static CONF_PARSER module_config[] = {
63 { "pwdfile", PW_TYPE_STRING_PTR, offsetof(x99_token_t, pwdfile),
65 { "syncdir", PW_TYPE_STRING_PTR, offsetof(x99_token_t, syncdir),
67 { "challenge_prompt", PW_TYPE_STRING_PTR, offsetof(x99_token_t,chal_prompt),
68 NULL, CHALLENGE_PROMPT },
69 { "challenge_length", PW_TYPE_INTEGER, offsetof(x99_token_t, chal_len),
71 { "softfail", PW_TYPE_INTEGER, offsetof(x99_token_t, softfail),
73 { "hardfail", PW_TYPE_INTEGER, offsetof(x99_token_t, hardfail),
75 { "allow_sync", PW_TYPE_BOOLEAN, offsetof(x99_token_t, allow_sync),
77 { "fast_sync", PW_TYPE_BOOLEAN, offsetof(x99_token_t, fast_sync),
79 { "allow_async", PW_TYPE_BOOLEAN, offsetof(x99_token_t, allow_async),
81 { "challenge_req", PW_TYPE_STRING_PTR, offsetof(x99_token_t, chal_req),
82 NULL, CHALLENGE_REQ },
83 { "resync_req", PW_TYPE_STRING_PTR, offsetof(x99_token_t, resync_req),
85 { "ewindow_size", PW_TYPE_INTEGER, offsetof(x99_token_t, ewindow_size),
87 { "maxdelay", PW_TYPE_INTEGER, offsetof(x99_token_t, maxdelay),
89 { "mschapv2_mppe", PW_TYPE_INTEGER,
90 offsetof(x99_token_t, mschapv2_mppe_policy), NULL, "2" },
91 { "mschapv2_mppe_bits", PW_TYPE_INTEGER,
92 offsetof(x99_token_t, mschapv2_mppe_types), NULL, "2" },
93 { "mschap_mppe", PW_TYPE_INTEGER,
94 offsetof(x99_token_t, mschap_mppe_policy), NULL, "2" },
95 { "mschap_mppe_bits", PW_TYPE_INTEGER,
96 offsetof(x99_token_t, mschap_mppe_types), NULL, "2" },
98 { "twindow_min", PW_TYPE_INTEGER, offsetof(x99_token_t, twindow_min),
100 { "twindow_max", PW_TYPE_INTEGER, offsetof(x99_token_t, twindow_max),
104 { NULL, -1, 0, NULL, NULL } /* end the list */
108 /* per-module initialization */
112 if ((rnd_fd = open(DEVURANDOM, O_RDONLY)) == -1) {
113 x99_log(X99_LOG_ERR, "init: error opening %s: %s", DEVURANDOM,
118 /* Generate a random key, used to protect the State attribute. */
119 if (x99_get_random(rnd_fd, hmac_key, sizeof(hmac_key)) == -1) {
120 x99_log(X99_LOG_ERR, "init: failed to obtain random data for hmac_key");
124 /* Initialize the password encoding/checking functions. */
131 /* per-instance initialization */
133 x99_token_instantiate(CONF_SECTION *conf, void **instance)
139 /* Set up a storage area for instance data. */
140 data = rad_malloc(sizeof(*data));
142 /* If the configuration parameters can't be parsed, then fail. */
143 if (cf_section_parse(conf, data, module_config) < 0) {
148 /* Verify ranges for those vars that are limited. */
149 if (data->chal_len < 5 || data->chal_len > MAX_CHALLENGE_LEN) {
152 "invalid challenge length, range 5-%d, using default of 6",
157 /* Enforce a single "%" sequence, which must be "%s" */
158 p = strchr(data->chal_prompt, '%');
159 if (p == NULL || p != strrchr(data->chal_prompt, '%') || strncmp(p,"%s",2)){
160 free(data->chal_prompt);
161 data->chal_prompt = strdup(CHALLENGE_PROMPT);
163 "invalid challenge prompt, using default of \"%s\"",
167 if (data->softfail < 0 ) {
169 x99_log(X99_LOG_ERR, "softfail must be at least 1 "
170 "(or 0 == infinite), using default of 5");
173 if (data->hardfail < 0 ) {
175 x99_log(X99_LOG_ERR, "hardfail must be at least 1 "
176 "(or 0 == infinite), using default of 0");
179 if (data->fast_sync && !data->allow_sync) {
181 x99_log(X99_LOG_INFO,
182 "fast_sync is yes, but allow_sync is no; disabling fast_sync");
185 if (!data->allow_sync && !data->allow_async) {
187 "at least one of {allow_async, allow_sync} must be set");
192 if (data->ewindow_size > MAX_EWINDOW_SIZE || data->ewindow_size < 0) {
193 data->ewindow_size = 0;
194 x99_log(X99_LOG_ERR, "max event window size is %d, using default of 0",
198 if (data->mschapv2_mppe_policy > 2 || data->mschapv2_mppe_policy < 0) {
199 data->mschapv2_mppe_policy = 2;
201 "invalid value for mschapv2_mppe, using default of 2");
204 if (data->mschapv2_mppe_types > 2 || data->mschapv2_mppe_types < 0) {
205 data->mschapv2_mppe_types = 2;
207 "invalid value for mschapv2_mppe_bits, using default of 2");
210 if (data->mschap_mppe_policy > 2 || data->mschap_mppe_policy < 0) {
211 data->mschap_mppe_policy = 2;
213 "invalid value for mschap_mppe, using default of 2");
216 if (data->mschap_mppe_types != 2) {
217 data->mschap_mppe_types = 2;
219 "invalid value for mschap_mppe_bits, using default of 2");
223 if (data->twindow_max - data->twindow_min > MAX_TWINDOW_SIZE) {
224 data->twindow_min = data->twindow_max = 0;
225 x99_log(X99_LOG_ERR, "max time window size is %d, using default of 0",
228 if (data->twindow_min > 0 || data->twindow_max < 0 ||
229 data->twindow_max < data->twindow_min) {
230 data->twindow_min = data->twindow_max = 0;
232 "invalid values for time window, using default of 0");
236 if (stat(data->syncdir, &st) != 0) {
237 x99_log(X99_LOG_ERR, "syncdir %s error: %s",
238 data->syncdir, strerror(errno));
242 if (st.st_mode != (S_IFDIR|S_IRWXU)) {
243 x99_log(X99_LOG_ERR, "syncdir %s has loose permissions", data->syncdir);
253 /* Generate a challenge to be presented to the user. */
255 x99_token_authorize(void *instance, REQUEST *request)
257 x99_token_t *inst = (x99_token_t *) instance;
259 char challenge[MAX_CHALLENGE_LEN + 1]; /* +1 for '\0' terminator */
263 x99_user_info_t user_info;
264 int user_found, auth_type_found;
266 int32_t sflags = 0; /* flags for state */
269 /* Early exit if Auth-Type != x99_token */
271 if ((vp = pairfind(request->config_items, PW_AUTHTYPE)) != NULL) {
273 if (strcmp(vp->strvalue, "x99_token")) {
274 return RLM_MODULE_NOOP;
278 /* The State attribute will be present if this is a response. */
279 if (pairfind(request->packet->vps, PW_STATE) != NULL) {
280 DEBUG("rlm_x99_token: autz: Found response to access challenge");
281 return RLM_MODULE_OK;
284 /* User-Name attribute required. */
285 if (!request->username) {
286 x99_log(X99_LOG_AUTH,
287 "autz: Attribute \"User-Name\" required for authentication.");
288 return RLM_MODULE_INVALID;
291 if ((pwattr = x99_pw_present(request)) == 0) {
292 x99_log(X99_LOG_AUTH, "autz: Attribute \"User-Password\" "
293 "or equivalent required for authentication.");
294 return RLM_MODULE_INVALID;
297 /* Look up the user's info. */
299 if ((rc = x99_get_user_info(inst->pwdfile, request->username->strvalue,
300 &user_info)) == -2) {
301 x99_log(X99_LOG_ERR, "autz: error reading user [%s] info",
302 request->username->strvalue);
303 return RLM_MODULE_FAIL;
306 /* x99_get_user_info() also logs, but we want to record the autz bit */
307 x99_log(X99_LOG_AUTH, "autz: user [%s] not found",
308 request->username->strvalue);
309 memset(&user_info, 0, sizeof(user_info)); /* X99_CF_NONE */
313 /* fast_sync mode (challenge only if requested) */
314 if (inst->fast_sync &&
315 ((user_info.card_id & X99_CF_SM) || !user_found)) {
317 if ((x99_pw_valid(request, inst, pwattr, inst->resync_req, NULL) &&
318 /* Set a bit indicating resync */ (sflags |= htonl(1))) ||
319 x99_pw_valid(request, inst, pwattr, inst->chal_req, NULL)) {
321 * Generate a challenge if requested. We don't test for card
322 * support [for async] because it's tricky for unknown users.
323 * Some configurations would have a problem where known users
324 * cannot request a challenge, but unknown users can. This
325 * reveals information. The easiest fix seems to be to always
326 * hand out a challenge on request.
327 * We also don't test if the server allows async mode, this
328 * would also reveal information.
330 DEBUG("rlm_x99_token: autz: fast_sync challenge requested");
335 * Otherwise, this is the token sync response. Signal
336 * the authenticate code to ignore State. We don't need
337 * to set a value, /existence/ of the vp is the signal.
339 if ((vp = paircreate(PW_X99_FAST, PW_TYPE_INTEGER)) == NULL) {
340 x99_log(X99_LOG_CRIT, "autz: no memory");
341 return RLM_MODULE_FAIL;
343 pairadd(&request->config_items, vp);
344 DEBUG("rlm_x99_token: autz: using fast_sync");
346 if (!auth_type_found)
347 pairadd(&request->config_items,
348 pairmake("Auth-Type", "x99_token", T_OP_EQ));
349 return RLM_MODULE_OK;
352 } /* if (fast_sync && card supports sync mode) */
355 /* Set the resync bit by default if the user can't request it. */
356 if (!inst->fast_sync)
359 /* Generate a random challenge. */
360 if (x99_get_challenge(rnd_fd, challenge, inst->chal_len) == -1) {
361 x99_log(X99_LOG_ERR, "autz: failed to obtain random challenge");
362 return RLM_MODULE_FAIL;
366 * Create the State attribute, which will be returned to us along with
367 * the response. We will need this to verify the response. Create
368 * a strong state if the user will be able use this with their token.
369 * Otherwise, we discard it anyway, so don't "waste" time with hmac.
370 * We also don't do the hmac if the user wasn't found (mask won't match).
371 * We always create at least a trivial state, so x99_token_authorize()
372 * can easily pass on to x99_token_authenticate().
374 if (user_info.card_id & X99_CF_AM) {
375 time_t now = time(NULL);
377 if (sizeof(now) != 4 || sizeof(long) != 4) {
378 x99_log(X99_LOG_ERR, "autz: only ILP32 arch is supported");
379 return RLM_MODULE_FAIL;
383 if (x99_gen_state(&state, NULL, challenge, sflags, now, hmac_key) != 0){
384 x99_log(X99_LOG_ERR, "autz: failed to generate state");
385 return RLM_MODULE_FAIL;
388 /* x2 b/c pairmake() string->octet needs even num of digits */
389 state = rad_malloc(3 + inst->chal_len * 2);
390 (void) sprintf(state, "0x%s%s", challenge, challenge);
392 pairadd(&request->reply->vps, pairmake("State", state, T_OP_EQ));
395 /* Add the challenge to the reply. */
397 char *u_challenge; /* challenge with addt'l presentation text */
399 u_challenge = rad_malloc(strlen(inst->chal_prompt)+MAX_CHALLENGE_LEN+1);
400 (void) sprintf(u_challenge, inst->chal_prompt, challenge);
401 pairadd(&request->reply->vps,
402 pairmake("Reply-Message", u_challenge, T_OP_EQ));
407 * Mark the packet as an Access-Challenge packet.
408 * The server will take care of sending it to the user.
410 request->reply->code = PW_ACCESS_CHALLENGE;
411 DEBUG("rlm_x99_token: Sending Access-Challenge.");
413 if (!auth_type_found)
414 pairadd(&request->config_items,
415 pairmake("Auth-Type", "x99_token", T_OP_EQ));
416 return RLM_MODULE_HANDLED;
420 /* Verify the response entered by the user. */
422 x99_token_authenticate(void *instance, REQUEST *request)
424 x99_token_t *inst = (x99_token_t *) instance;
426 x99_user_info_t user_info;
429 int32_t sflags = 0; /* flags from state */
432 char challenge[MAX_CHALLENGE_LEN + 1];
433 char e_response[9]; /* expected response */
434 VALUE_PAIR *add_vps = NULL;
436 /* User-Name attribute required. */
437 if (!request->username) {
438 x99_log(X99_LOG_AUTH,
439 "auth: Attribute \"User-Name\" required for authentication.");
440 return RLM_MODULE_INVALID;
442 username = request->username->strvalue;
444 if ((pwattr = x99_pw_present(request)) == 0) {
445 x99_log(X99_LOG_AUTH, "auth: Attribute \"User-Password\" "
446 "or equivalent required for authentication.");
447 return RLM_MODULE_INVALID;
450 /* Add a message to the auth log. */
451 pairadd(&request->packet->vps, pairmake("Module-Failure-Message",
452 X99_MODULE_NAME, T_OP_EQ));
453 pairadd(&request->packet->vps, pairmake("Module-Success-Message",
454 X99_MODULE_NAME, T_OP_EQ));
456 /* Look up the user's info. */
457 if (x99_get_user_info(inst->pwdfile, username, &user_info) != 0) {
458 x99_log(X99_LOG_AUTH, "auth: error reading user [%s] info", username);
459 return RLM_MODULE_REJECT;
462 /* Retrieve the challenge (from State attribute), unless (fast_sync). */
463 if (pairfind(request->config_items, PW_X99_FAST) == NULL) {
465 unsigned char *state;
468 if ((vp = pairfind(request->packet->vps, PW_STATE)) != NULL) {
469 int e_length = inst->chal_len;
471 /* Extend expected length if state should have been protected. */
472 if (user_info.card_id & X99_CF_AM)
473 e_length += 4 + 4 + 16; /* sflags + time + hmac */
475 if (vp->length != e_length) {
476 x99_log(X99_LOG_AUTH,
477 "auth: bad state for [%s]: length", username);
478 return RLM_MODULE_INVALID;
481 /* Fast path if we didn't protect the state. */
482 if (!(user_info.card_id & X99_CF_AM))
485 /* Verify the state. */
486 (void) memset(challenge, 0, sizeof(challenge));
487 (void) memcpy(challenge, vp->strvalue, inst->chal_len);
488 (void) memcpy(&sflags, vp->strvalue + inst->chal_len, 4);
489 (void) memcpy(&then, vp->strvalue + inst->chal_len + 4, 4);
490 if (x99_gen_state(NULL,&state,challenge,sflags,then,hmac_key) != 0){
491 x99_log(X99_LOG_ERR, "auth: failed to generate state");
492 return RLM_MODULE_FAIL;
494 if (memcmp(state, vp->strvalue, vp->length)) {
495 x99_log(X99_LOG_AUTH,
496 "auth: bad state for [%s]: hmac", username);
498 return RLM_MODULE_REJECT;
502 /* State is valid, but check expiry. */
504 if (then + inst->maxdelay < time(NULL)) {
505 x99_log(X99_LOG_AUTH,
506 "auth: bad state for [%s]: expired", username);
507 return RLM_MODULE_REJECT;
513 /* This should only happen if the authorize code didn't run. */
514 x99_log(X99_LOG_ERR, "auth: bad state for [%s]: missing "
515 "(is x99_token listed in radiusd.conf's authorize stanza?)",
517 return RLM_MODULE_FAIL;
519 } /* if (!fast_sync) */
521 /* Check failure count. */
522 if (x99_check_failcount(username, inst))
523 return RLM_MODULE_USERLOCK;
526 * Don't bother to check async response if either
527 * - the card doesn't support it, or
528 * - we're doing fast_sync.
530 if (!(user_info.card_id & X99_CF_AM) ||
531 pairfind(request->config_items, PW_X99_FAST)) {
535 /* Perform any site-specific transforms of the challenge. */
536 if (x99_challenge_transform(username, challenge) != 0) {
538 "auth: challenge transform failed for [%s]", username);
539 return RLM_MODULE_FAIL;
540 /* NB: last_auth, failcount not updated. */
543 /* Calculate and test the async response. */
544 if (x99_response(challenge, e_response, user_info.card_id,
545 user_info.keyblock) != 0) {
547 "auth: unable to calculate async response for [%s], "
548 "to challenge %s", username, challenge);
549 return RLM_MODULE_FAIL;
550 /* NB: last_auth, failcount not updated. */
552 DEBUG("rlm_x99_token: auth: [%s], async challenge %s, "
553 "expecting response %s", username, challenge, e_response);
555 if (x99_pw_valid(request, inst, pwattr, e_response, &add_vps)) {
556 /* Password matches. Is this allowed? */
557 if (!inst->allow_async) {
558 x99_log(X99_LOG_AUTH,
559 "auth: bad async for [%s]: disallowed by config", username);
560 rc = RLM_MODULE_REJECT;
561 goto return_pw_valid;
562 /* NB: last_auth, failcount not updated. */
565 /* Make sure this isn't a replay by forcing a delay. */
566 if (x99_get_last_auth(inst->syncdir, username, &last_auth) != 0) {
568 "auth: unable to get last auth time for [%s]", username);
569 return RLM_MODULE_FAIL;
571 if (last_auth + inst->maxdelay > time(NULL)) {
572 x99_log(X99_LOG_AUTH,
573 "auth: bad async for [%s]: too soon", username);
574 rc = RLM_MODULE_REJECT;
575 goto return_pw_valid;
576 /* NB: last_auth, failcount not updated. */
579 if (user_info.card_id & X99_CF_SM) {
580 x99_log(X99_LOG_INFO,
581 "auth: [%s] authenticated in async mode", username);
585 if (ntohl(sflags) & 1) {
587 * Resync the card. The sync data doesn't mean anything for
588 * async-only cards, but we want the side effects of resetting
589 * the failcount and the last auth time. We "fail-out" if we
590 * can't do this, because if we can't update the last auth time,
591 * we will be open to replay attacks over the lifetime of the
592 * State attribute (inst->maxdelay).
594 if (x99_get_sync_data(inst->syncdir, username, user_info.card_id,
595 1, 0, challenge, user_info.keyblock) != 0) {
596 x99_log(X99_LOG_ERR, "auth: unable to get sync data "
597 "e:%d t:%d for [%s] (for resync)", 1, 0, username);
598 rc = RLM_MODULE_FAIL;
599 } else if (x99_set_sync_data(inst->syncdir, username, challenge,
600 user_info.keyblock) != 0) {
602 "auth: unable to set sync data for [%s] (for resync)",
604 rc = RLM_MODULE_FAIL;
607 /* Just update last_auth, failcount. */
608 if (x99_reset_failcount(inst->syncdir, username) != 0) {
610 "auth: unable to reset failcount for [%s]", username);
611 rc = RLM_MODULE_FAIL;
614 goto return_pw_valid;
615 } /* if (user authenticated async) */
619 * Calculate and test sync responses in the window.
620 * Note that we always accept a sync response, even
621 * if a challenge or resync was explicitly requested.
623 if ((user_info.card_id & X99_CF_SM) && inst->allow_sync) {
624 for (i = 0; i <= inst->ewindow_size; ++i) {
625 /* Get sync challenge and key. */
626 if (x99_get_sync_data(inst->syncdir, username, user_info.card_id,
627 i, 0, challenge, user_info.keyblock) != 0) {
629 "auth: unable to get sync data e:%d t:%d for [%s]",
631 rc = RLM_MODULE_FAIL;
632 goto return_pw_valid;
633 /* NB: last_auth, failcount not updated. */
636 /* Calculate sync response. */
637 if (x99_response(challenge, e_response, user_info.card_id,
638 user_info.keyblock) != 0) {
639 x99_log(X99_LOG_ERR, "auth: unable to calculate sync response "
640 "e:%d t:%d for [%s], to challenge %s",
641 i, 0, username, challenge);
642 rc = RLM_MODULE_FAIL;
643 goto return_pw_valid;
644 /* NB: last_auth, failcount not updated. */
646 DEBUG("rlm_x99_token: auth: [%s], sync challenge %d %s, "
647 "expecting response %s", username, i, challenge, e_response);
649 /* Test user-supplied password. */
650 if (x99_pw_valid(request, inst, pwattr, e_response, &add_vps)) {
652 * Yay! User authenticated via sync mode. Resync.
654 * The same failure/replay issue applies here as in the
655 * identical code block in the async section above, with
656 * the additional problem that a response can be reused
657 * indefinitely! (until the sync data is updated)
660 if (x99_get_sync_data(inst->syncdir,username,user_info.card_id,
661 1, 0, challenge,user_info.keyblock) != 0){
662 x99_log(X99_LOG_ERR, "auth: unable to get sync data "
663 "e:%d t:%d for [%s] (for resync)", 1, 0, username);
664 rc = RLM_MODULE_FAIL;
665 } else if (x99_set_sync_data(inst->syncdir, username, challenge,
666 user_info.keyblock) != 0) {
668 "auth: unable to set sync data for [%s] "
669 "(for resync)", username);
670 rc = RLM_MODULE_FAIL;
672 goto return_pw_valid;
675 } /* for (each slot in the window) */
676 } /* if (card is in sync mode and sync mode allowed) */
678 /* Both async and sync mode failed. */
679 if (x99_incr_failcount(inst->syncdir, username) != 0) {
681 "auth: unable to increment failure count for user [%s]",
684 return RLM_MODULE_REJECT;
686 /* Must exit here after a successful return from x99_pw_valid(). */
689 /* Handle any vps returned from x99_pw_valid(). */
690 if (rc == RLM_MODULE_OK) {
691 pairadd(&request->reply->vps, add_vps);
699 /* per-instance destruction */
701 x99_token_detach(void *instance)
703 x99_token_t *inst = (x99_token_t *) instance;
707 free(inst->chal_prompt);
708 free(inst->chal_req);
709 free(inst->resync_req);
715 /* per-module destruction */
717 x99_token_destroy(void)
719 (void) memset(hmac_key, 0, sizeof(hmac_key));
720 (void) close(rnd_fd);
725 * If the module needs to temporarily modify it's instantiation
726 * data, the type should be changed to RLM_TYPE_THREAD_UNSAFE.
727 * The server will then take care of ensuring that the module
728 * is single-threaded.
730 module_t rlm_x99_token = {
732 RLM_TYPE_THREAD_SAFE, /* type */
733 x99_token_init, /* initialization */
734 x99_token_instantiate, /* instantiation */
736 x99_token_authenticate, /* authentication */
737 x99_token_authorize, /* authorization */
738 NULL, /* preaccounting */
739 NULL, /* accounting */
740 NULL, /* checksimul */
741 NULL, /* pre-proxy */
742 NULL, /* post-proxy */
745 x99_token_detach, /* detach */
746 x99_token_destroy, /* destroy */