/** * @param pPswd2check modhex encoded */ bool YubikoOtpKeyConfig::checkOtp(const std::string& pPswd2check) { BOOST_LOG_NAMED_SCOPE("YubikoOtpKeyConfig::checkPassword"); yubikey_token_st myToken; yubikey_parse(reinterpret_cast<const uint8_t*>(pPswd2check.c_str()), this->getSecretKeyArray().data(), &myToken); BOOST_LOG_TRIVIAL(debug)<< "Key token:"; logDebug_token(getToken()); BOOST_LOG_TRIVIAL(debug)<< "Decrypted token:"; logDebug_token(myToken); if (strncmp(reinterpret_cast<const char*>(&getToken().uid), reinterpret_cast<char*>(&myToken.uid), YUBIKEY_UID_SIZE) == 0) { BOOST_LOG_TRIVIAL(debug)<< "UID is same."; uint16_t myComputedCrc = computeCrc(myToken); if(myToken.crc!=myComputedCrc) { BOOST_LOG_TRIVIAL(debug)<< "Decrypted CRC is wrong: " << myComputedCrc <<"!=" << myToken.crc; return false; } if(myToken.ctr > getToken().ctr) { BOOST_LOG_TRIVIAL(debug)<< "Decrypted counter is bigger than stored value: " << int(myToken.ctr) <<">" << int(getToken().ctr) << " reseting use counter & clock."; getToken().use= myToken.use; copyAndSaveToken(myToken); BOOST_LOG_TRIVIAL(debug)<< "OTP OK (use counter reset)!"; return true; } else { if(myToken.ctr < getToken().ctr) { BOOST_LOG_TRIVIAL(debug)<< "Decrypted counter is smaller than stored value: " << int(myToken.ctr) <<"<" << int(getToken().ctr) << " returning false."; return false; } } BOOST_LOG_TRIVIAL(debug)<< "Counter is "<< int(myToken.ctr)<<"."; if(myToken.use <= getToken().use) { BOOST_LOG_TRIVIAL(debug)<< "Decrypted use counter is wrong: " << int(myToken.use) <<"<=" << int(getToken().use); return false; } UTimestamp myTstmp; myTstmp.tstp.tstph=myToken.tstph; myTstmp.tstp.tstpl=myToken.tstpl; if(myTstmp.tstp_int<=getTimestamp().tstp_int) { BOOST_LOG_TRIVIAL(debug)<< "Decrypted timer is smaller than stored value: " << myTstmp.tstp_int <<"<=" << getTimestamp().tstp_int << " returning false."; return false; } else { BOOST_LOG_TRIVIAL(debug)<< "Decrypted timer int value: " << myTstmp.tstp_int <<"."; } copyAndSaveToken(myToken); BOOST_LOG_TRIVIAL(debug)<< "OTP OK!"; return true; } return false; }
int check_user_token(char *user, char *token, sqlite3 *db) { char *request; sqlite3_stmt *ppStmt; entry_st entry; yubikey_token_st tok; int id, rc; request = sqlite3_mprintf("SELECT id FROM tokens WHERE user=%Q;", user); rc = sqlite3_prepare_v2(db, request, -1, &ppStmt, NULL); sqlite3_free(request); if(check_error("Error in preparing SELECT", rc, db)) return CHK_FAIL; rc = sqlite3_step(ppStmt); if (rc != SQLITE_ROW) { sqlite3_finalize(ppStmt); return CHK_UNKNOWN; } if (strlen(token) != 32) { sqlite3_finalize(ppStmt); return CHK_FAIL; } while (rc == SQLITE_ROW) { id = sqlite3_column_int(ppStmt, 0); get_entry_from_id(id, &entry, db); yubikey_parse ((uint8_t *) token, entry.aes_bin, &tok); rc = check_token(&tok, entry.uid, entry.counter, entry.session); if (rc == CHK_OK) { rc = update_db(db, id, &tok); sqlite3_finalize(ppStmt); return rc; } rc = sqlite3_step(ppStmt); } sqlite3_finalize(ppStmt); return CHK_FAIL; }
static void otp_test1 (void) { yubikey_token_st tok; char out[1024]; uint8_t key[16 + 1]; /* Test OTP */ memcpy ((void *) &tok, "\x16\xe1\xe5\xd9\xd3\x99\x10\x04\x45\x20\x07\xe3\x02\x00\x00", 16); memcpy (key, "abcdef0123456789", 16); yubikey_generate ((void *) &tok, key, out); yubikey_parse ((uint8_t *) out, key, &tok); assert (memcmp (&tok, "\x16\xe1\xe5\xd9\xd3\x99\x10\x04\x45\x20\x07\xe3\x02\x00\x00", 16) == 0); printf ("OTP-1 success\n"); }
int main (int argc, char *argv[]) { uint8_t buf[128]; uint8_t key[YUBIKEY_KEY_SIZE]; char *aeskey, *token; yubikey_token_st tok; /* Parse command line parameters. */ if (argc <= 2) { printf ("Usage: %s <aeskey> <token>\n", argv[0]); printf (" AESKEY:\tHex encoded AES-key.\n"); printf (" TOKEN:\t\tModHex encoded token.\n"); return EXIT_FAILURE; } aeskey = argv[1]; token = argv[2]; if (strlen (aeskey) != 32) { printf ("error: Hex encoded AES-key must be 32 characters.\n"); return EXIT_FAILURE; } if (strlen (token) > 32) { printf ("warning: overlong token, ignoring prefix: %.*s\n", (int) strlen (token) - 32, token); token = token + (strlen (token) - 32); } if (strlen (token) != 32) { printf ("error: ModHex encoded token must be 32 characters.\n"); return EXIT_FAILURE; } /* Debug. */ printf ("Input:\n"); printf (" token: %s\n", token); yubikey_modhex_decode ((char *) key, token, YUBIKEY_KEY_SIZE); { size_t i; printf (" "); for (i = 0; i < YUBIKEY_KEY_SIZE; i++) printf ("%02x ", key[i] & 0xFF); printf ("\n"); } printf (" aeskey: %s\n", aeskey); yubikey_hex_decode ((char *) key, aeskey, YUBIKEY_KEY_SIZE); { size_t i; printf (" "); for (i = 0; i < YUBIKEY_KEY_SIZE; i++) printf ("%02x ", key[i] & 0xFF); printf ("\n"); } /* Pack up dynamic password, decrypt it and verify checksum */ yubikey_parse ((uint8_t *) token, key, &tok); printf ("Output:\n"); { size_t i; printf (" "); for (i = 0; i < YUBIKEY_BLOCK_SIZE; i++) printf ("%02x ", ((uint8_t *) & tok)[i] & 0xFF); printf ("\n"); } printf ("\nStruct:\n"); /* Debug */ { size_t i; printf (" uid: "); for (i = 0; i < YUBIKEY_UID_SIZE; i++) printf ("%02x ", tok.uid[i] & 0xFF); printf ("\n"); } printf (" counter: %d (0x%04x)\n", tok.ctr, tok.ctr); printf (" timestamp (low): %d (0x%04x)\n", tok.tstpl, tok.tstpl); printf (" timestamp (high): %d (0x%02x)\n", tok.tstph, tok.tstph); printf (" session use: %d (0x%02x)\n", tok.use, tok.use); printf (" random: %d (0x%02x)\n", tok.rnd, tok.rnd); printf (" crc: %d (0x%04x)\n", tok.crc, tok.crc); printf ("\nDerived:\n"); printf (" cleaned counter: %d (0x%04x)\n", yubikey_counter (tok.ctr), yubikey_counter (tok.ctr)); yubikey_modhex_encode ((char *) buf, (char *) tok.uid, YUBIKEY_UID_SIZE); printf (" modhex uid: %s\n", buf); printf (" triggered by caps lock: %s\n", yubikey_capslock (tok.ctr) ? "yes" : "no"); printf (" crc: %04X\n", yubikey_crc16 ((void *) &tok, YUBIKEY_KEY_SIZE)); printf (" crc check: "); if (yubikey_crc_ok_p ((uint8_t *) & tok)) { printf ("ok\n"); return EXIT_SUCCESS; } printf ("fail\n"); return EXIT_FAILURE; }
/** Decrypt a Yubikey OTP AES block * * @param inst Module configuration. * @param passcode string to decrypt. * @return one of the RLM_RCODE_* constants. */ rlm_rcode_t rlm_yubikey_decrypt(rlm_yubikey_t *inst, REQUEST *request, char const *passcode) { uint32_t counter; yubikey_token_st token; DICT_ATTR const *da; char private_id[(YUBIKEY_UID_SIZE * 2) + 1]; VALUE_PAIR *key, *vp; da = dict_attrbyname("Yubikey-Key"); if (!da) { REDEBUG("Dictionary missing entry for 'Yubikey-Key'"); return RLM_MODULE_FAIL; } key = pairfind(request->config_items, da->attr, da->vendor, TAG_ANY); if (!key) { REDEBUG("Yubikey-Key attribute not found in control list, can't decrypt OTP data"); return RLM_MODULE_INVALID; } if (key->length != YUBIKEY_KEY_SIZE) { REDEBUG("Yubikey-Key length incorrect, expected %u got %zu", YUBIKEY_KEY_SIZE, key->length); return RLM_MODULE_INVALID; } yubikey_parse((uint8_t const *) passcode + inst->id_len, key->vp_octets, &token); /* * Apparently this just uses byte offsets... */ if (!yubikey_crc_ok_p((uint8_t *) &token)) { REDEBUG("Decrypting OTP token data failed, rejecting"); return RLM_MODULE_REJECT; } RDEBUG("Token data decrypted successfully"); if (request->log.lvl && request->log.func) { (void) fr_bin2hex((char *) &private_id, (uint8_t*) &token.uid, YUBIKEY_UID_SIZE); RDEBUG2("Private ID : 0x%s", private_id); RDEBUG2("Session counter : %u", yubikey_counter(token.ctr)); RDEBUG2("# used in session : %u", token.use); RDEBUG2("Token timestamp : %u", (token.tstph << 16) | token.tstpl); RDEBUG2("Random data : %u", token.rnd); RDEBUG2("CRC data : 0x%x", token.crc); } /* * Private ID used for validation purposes */ vp = pairmake(request, &request->packet->vps, "Yubikey-Private-ID", NULL, T_OP_SET); if (!vp) { REDEBUG("Failed creating Yubikey-Private-ID"); return RLM_MODULE_FAIL; } pairmemcpy(vp, token.uid, YUBIKEY_UID_SIZE); /* * Token timestamp */ vp = pairmake(request, &request->packet->vps, "Yubikey-Timestamp", NULL, T_OP_SET); if (!vp) { REDEBUG("Failed creating Yubikey-Timestamp"); return RLM_MODULE_FAIL; } vp->vp_integer = (token.tstph << 16) | token.tstpl; vp->length = 4; /* * Token random */ vp = pairmake(request, &request->packet->vps, "Yubikey-Random", NULL, T_OP_SET); if (!vp) { REDEBUG("Failed creating Yubikey-Random"); return RLM_MODULE_FAIL; } vp->vp_integer = token.rnd; vp->length = 4; /* * Combine the two counter fields together so we can do * replay attack checks. */ counter = (yubikey_counter(token.ctr) << 16) | token.use; vp = pairmake(request, &request->packet->vps, "Yubikey-Counter", NULL, T_OP_SET); if (!vp) { REDEBUG("Failed creating Yubikey-Counter"); return RLM_MODULE_FAIL; } vp->vp_integer = counter; vp->length = 4; /* * Now we check for replay attacks */ vp = pairfind(request->config_items, vp->da->attr, vp->da->vendor, TAG_ANY); if (!vp) { RWDEBUG("Yubikey-Counter not found in control list, skipping replay attack checks"); return RLM_MODULE_OK; } if (counter <= vp->vp_integer) { REDEBUG("Replay attack detected! Counter value %u, is lt or eq to last known counter value %u", counter, vp->vp_integer); return RLM_MODULE_REJECT; } return RLM_MODULE_OK; }
int main (void) { char buf[1024]; size_t i; int rc; yubikey_token_st tok; /* Test Modhex */ yubikey_modhex_encode (buf, "test", 4); printf ("modhex-encode(\"test\") = %s\n", buf); if (strcmp (buf, "ifhgieif") != 0) { printf ("ModHex failure\n"); return 1; } printf ("Modhex-1 success\n"); printf ("modhex-decode(\"%s\") = ", buf); yubikey_modhex_decode (buf, buf, strlen ((char *) buf)); printf ("%.*s\n", 4, buf); if (memcmp (buf, "test", 4) != 0) { printf ("ModHex failure\n"); return 1; } printf ("Modhex-2 success\n"); strcpy (buf, "cbdefghijklnrtuv"); rc = yubikey_modhex_p (buf); printf ("hex-p(\"%s\") = %d\n", buf, rc); if (!rc) { printf ("Hex_p failure\n"); return 1; } printf ("Hex-3 success\n"); strcpy (buf, "0123Xabc"); rc = yubikey_hex_p (buf); printf ("hex-p(\"%s\") = %d\n", buf, rc); if (rc) { printf ("Hex_p failure\n"); return 1; } printf ("Hex-3 success\n"); /* Test Hex */ yubikey_hex_encode (buf, "test", 4); printf ("hex-encode(\"test\") = %s\n", buf); if (strcmp (buf, "74657374") != 0) { printf ("Hex failure\n"); return 1; } printf ("Hex-1 success\n"); printf ("hex-decode(\"%s\") = ", buf); yubikey_hex_decode (buf, buf, strlen ((char *) buf)); printf ("%.*s\n", 4, buf); if (memcmp (buf, "test", 4) != 0) { printf ("Hex failure\n"); return 1; } printf ("Hex-2 success\n"); strcpy (buf, "0123456789abcdef"); rc = yubikey_hex_p (buf); printf ("hex-p(\"%s\") = %d\n", buf, rc); if (!rc) { printf ("Hex_p failure\n"); return 1; } printf ("Hex-3 success\n"); strcpy (buf, "0123Xabc"); rc = yubikey_hex_p (buf); printf ("hex-p(\"%s\") = %d\n", buf, rc); if (rc) { printf ("Hex_p failure\n"); return 1; } printf ("Hex-3 success\n"); /* Test AES */ { uint8_t buf[1024]; char out[1024]; uint8_t key[16 + 1]; memcpy (buf, "0123456789abcdef\0", 17); memcpy (key, "abcdef0123456789\0", 17); printf ("aes-decrypt (data=%s, key=%s)\n => ", (char *) buf, (char *) key); yubikey_aes_decrypt (buf, key); for (i = 0; i < 16; i++) printf ("%02x", buf[i] & 0xFF); printf ("\n"); if (memcmp (buf, "\x83\x8a\x46\x7f\x34\x63\x95\x51" "\x75\x5b\xd3\x2a\x4a\x2f\x15\xe1", 16) != 0) { printf ("AES failure\n"); return 1; } printf ("AES-1 success\n"); yubikey_aes_encrypt (buf, key); if (memcmp (buf, "0123456789abcdef", 16) != 0) { printf ("AES encryption failure\n"); return 1; } printf ("AES-2 success\n"); /* Test OTP */ memcpy ((void *) &tok, "\x16\xe1\xe5\xd9\xd3\x99\x10\x04\x45\x20\x07\xe3\x02\x00\x00", 16); memcpy (key, "abcdef0123456789", 16); yubikey_generate ((void *) &tok, key, out); yubikey_parse ((uint8_t *) out, key, &tok); if (memcmp (&tok, "\x16\xe1\xe5\xd9\xd3\x99\x10\x04\x45\x20\x07\xe3\x02\x00\x00", 16) != 0) { printf ("OTP generation - parse failure\n"); return 1; } printf ("OTP-1 success\n"); } return 0; }
static int yubikey_auth_core(myConf_t *myConf, REQUEST *request) { int passLen = 0, session = 0, counter = 0, i = 0; MD5_CTX ctx; int result = 0; char *filename = "/usr/local/etc/raddb/yubico/users"; //get password by removing the last 32 characters of the password if (strlen(request->password->vp_strvalue) <= 32) { DEBUG("rlm_yubikey: Password too short."); return RLM_MODULE_REJECT; } passLen = strlen(request->password->vp_strvalue) - 32; strncpy(myConf->pass, request->password->vp_strvalue, passLen); myConf->pass[passLen] = '\0'; strncpy(myConf->token, request->password->vp_strvalue + passLen, 32); myConf->token[32] = '\0'; //md5 stuff MD5Init(&ctx); DEBUG("rlm_yubikey: length: %d, string: %s", passLen, myConf->pass); MD5Update(&ctx, (unsigned char *)myConf->pass, passLen); MD5Final(&ctx); MD5toString(&ctx, myConf->md5ComputedString); myConf->md5ComputedString[32] = '\0'; DEBUG("rlm_yubikey: MD5string of your pass: %s", myConf->md5ComputedString); DEBUG("rlm_yubikey: Username: %s", request->username->vp_strvalue); //read file result = config_read_file(&(myConf->config), filename); if (result != CONFIG_TRUE) { DEBUG("rlm_yubikey: Failed to parse configuration file: config_read_file (&config, filename);"); DEBUG("config_error_text()= %s and config_error_line()=%d", config_error_text(&(myConf->config)), config_error_line(&(myConf->config))); return RLM_MODULE_FAIL; } //parse file myConf->config_setting = config_lookup(&(myConf->config), USERS_PATH); if (myConf->config_setting == NULL) { DEBUG("rlm_yubikey: Failed to parse configuration file: config_lookup failed to find the users node"); return RLM_MODULE_FAIL; } //go through the list of users for (i = 0; i < config_setting_length(myConf->config_setting); i++) { DEBUG("Trying i: %d", i); config_setting_t *tmpSetting = NULL; tmpSetting = config_setting_get_elem(myConf->config_setting, i); if (tmpSetting == NULL) { DEBUG("rlm_yubikey: Failed to parse configuration file: config_setting_get_elem(config_setting,i);"); return RLM_MODULE_FAIL; } if ((config_setting_get_string_elem(tmpSetting, 0) == NULL) || (config_setting_get_string_elem(tmpSetting, 1) == NULL) || (config_setting_get_string_elem(tmpSetting, 2) == NULL)) { DEBUG("rlm_yubikey: Failed to parse configuration file while reading the username/password/aeskey triplet "); return RLM_MODULE_FAIL; } //check usernames are equal if (strcmp(request->username->vp_strvalue, config_setting_get_string_elem(tmpSetting, 0)) != 0) { //users do not match. No need to debug this //Go to next iteration continue; } //check passwords are equal if (strcmp(myConf->md5ComputedString, config_setting_get_string_elem(tmpSetting, 1)) != 0) { //passwords do not match. We debug DEBUG("rlm_yubikey: Password does not match for user %s", request->username->vp_strvalue); //Go to next iteration continue; } //check aes stuff - mostly copied from the ykdebug.c that comes with the low-level yubikey library uint8_t buf[128]; const char *aeskey = config_setting_get_string_elem(tmpSetting, 2); char *token = myConf->token; uint8_t key[YUBIKEY_KEY_SIZE]; yubikey_token_st tok; yubikey_modhex_decode((char*) key, token, YUBIKEY_KEY_SIZE); DEBUG("rlm_yubikey: aeskey: %s", aeskey); yubikey_hex_decode((char*) key, aeskey, YUBIKEY_KEY_SIZE); /* Pack up dynamic password, decrypt it and verify checksum */ yubikey_parse((uint8_t*) token, key, &tok); DEBUG("rlm_yubikey: Struct:"); size_t i; char *tmp = (char*) malloc(1024); for (i = 0; i < YUBIKEY_UID_SIZE; i++) { sprintf(tmp + i, "%c ", tok.uid[i] & 0xFF); } tmp[YUBIKEY_UID_SIZE + i] = 0; DEBUG("rlm_yubikey: uid:%s", tmp); free(tmp); DEBUG("rlm_yubikey: counter: %d (0x%04x)", tok.ctr, tok.ctr); DEBUG("rlm_yubikey: timestamp (low): %d (0x%04x)", tok.tstpl, tok.tstpl); DEBUG("rlm_yubikey: timestamp (high): %d (0x%02x)", tok.tstph, tok.tstph); DEBUG("rlm_yubikey: session use: %d (0x%02x)", tok.use, tok.use); DEBUG("rlm_yubikey: random: %d (0x%02x)", tok.rnd, tok.rnd); DEBUG("rlm_yubikey: crc: %d (0x%04x)", tok.crc, tok.crc); DEBUG("rlm_yubikey: Derived:"); DEBUG("rlm_yubikey: cleaned counter: %d (0x%04x)",yubikey_counter(tok.ctr), yubikey_counter(tok.ctr)); yubikey_modhex_encode((char*) buf, (char*) tok.uid, YUBIKEY_UID_SIZE); DEBUG("rlm_yubikey: modhex uid: %s", buf); DEBUG("rlm_yubikey: triggered by caps lock: %s", yubikey_capslock(tok.ctr) ? "yes" : "no"); DEBUG("rlm_yubikey: crc: %04X", yubikey_crc16((void*) & tok, YUBIKEY_KEY_SIZE)); DEBUG("rlm_yubikey: crc check: "); if (yubikey_crc_ok_p((uint8_t*) & tok)) { DEBUG("rlm_yubikey: ok"); char *tmppath = KEYS_PATH; char *path = (char*) malloc(strlen(tmppath) + 32 + 1); strcpy(path, tmppath); strcat(path, aeskey); myConf->config_setting = config_lookup(&(myConf->config), path); if (myConf->config_setting == NULL) { DEBUG("rlm_yubikey: Error parsing file: %s not found", path); return RLM_MODULE_FAIL; } //checking counter and session and update them if necessary counter = config_setting_get_int_elem(myConf->config_setting, 0); session = config_setting_get_int_elem(myConf->config_setting, 1); DEBUG("rlm_yubikey: Read counter: %d, session: %d", counter, session); if ((tok.ctr < counter)||((tok.ctr == counter) && (tok.use <= session))) { DEBUG("rlm_yubikey: someone tried to login with an old generated hash"); return RLM_MODULE_REJECT; } //updating config file with counter and session config_setting_set_int_elem(myConf->config_setting, 0, tok.ctr); config_setting_set_int_elem(myConf->config_setting, 1, tok.use); DEBUG("rlm_yubikey: Written element: %ld", config_setting_get_int_elem(myConf->config_setting, 0)); DEBUG("rlm_yubikey: Written element: %ld", config_setting_get_int_elem(myConf->config_setting, 1)); if (CONFIG_FALSE == config_write_file(&(myConf->config), filename)) { DEBUG("rlm_yubikey: Failed to write the file."); return RLM_MODULE_FAIL; } return RLM_MODULE_OK; } DEBUG("rlm_yubikey: fail"); } DEBUG("rlm_yubikey: Authenticating with password %s", request->password->vp_strvalue); return RLM_MODULE_REJECT; }
static int yubikey_login(const char *username, const char *password) { char fn[MAXPATHLEN]; char hexkey[33], key[YUBIKEY_KEY_SIZE]; char hexuid[13], uid[YUBIKEY_UID_SIZE]; FILE *f; yubikey_token_st tok; u_int32_t last_ctr = 0, ctr; int r, i = 0, mapok = 0, crcok = 0; snprintf(fn, sizeof(fn), "%s/%s.uid", path, username); if ((f = fopen(fn, "r")) == NULL) { syslog(LOG_ERR, "user %s: fopen: %s: %m", username, fn); return (AUTH_FAILED); } if (fscanf(f, "%12s", hexuid) != 1) { syslog(LOG_ERR, "user %s: fscanf: %s: %m", username, fn); fclose(f); return (AUTH_FAILED); } fclose(f); snprintf(fn, sizeof(fn), "%s/%s.key", path, username); if ((f = fopen(fn, "r")) == NULL) { syslog(LOG_ERR, "user %s: fopen: %s: %m", username, fn); return (AUTH_FAILED); } if (fscanf(f, "%32s", hexkey) != 1) { syslog(LOG_ERR, "user %s: fscanf: %s: %m", username, fn); fclose(f); return (AUTH_FAILED); } fclose(f); if (strlen(hexkey) != 32) { syslog(LOG_ERR, "user %s: key len != 32", username); return (AUTH_FAILED); } snprintf(fn, sizeof(fn), "%s/%s.ctr", path, username); if ((f = fopen(fn, "r")) != NULL) { if (fscanf(f, "%u", &last_ctr) != 1) last_ctr = 0; fclose(f); } yubikey_hex_decode(uid, hexuid, YUBIKEY_UID_SIZE); yubikey_hex_decode(key, hexkey, YUBIKEY_KEY_SIZE); /* * Cycle through the key mapping table. * XXX brute force, unoptimized; a lookup table for valid mappings may * be appropriate. */ while (1) { r = yubikey_parse((uint8_t *)password, (uint8_t *)key, &tok, i++); switch (r) { case EMSGSIZE: syslog(LOG_INFO, "user %s failed: password too short.", username); return (AUTH_FAILED); case EINVAL: /* keyboard mapping invalid */ continue; case 0: /* found a valid keyboard mapping */ mapok++; if (!yubikey_crc_ok_p((uint8_t *)&tok)) continue; /* try another one */ crcok++; syslog(LOG_DEBUG, "user %s: crc %04x ok", username, tok.crc); if (memcmp(tok.uid, uid, YUBIKEY_UID_SIZE)) { char h[13]; yubikey_hex_encode(h, (const char *)tok.uid, YUBIKEY_UID_SIZE); syslog(LOG_DEBUG, "user %s: uid %s != %s", username, h, hexuid); continue; /* try another one */ } break; /* uid matches */ case -1: syslog(LOG_INFO, "user %s: could not decode password " "with any keymap (%d crc ok)", username, crcok); return (AUTH_FAILED); default: syslog(LOG_DEBUG, "user %s failed: %s", username, strerror(r)); return (AUTH_FAILED); } break; /* only reached through the bottom of case 0 */ } syslog(LOG_INFO, "user %s uid %s: %d matching keymaps (%d checked), " "%d crc ok", username, hexuid, mapok, i, crcok); ctr = ((u_int32_t)yubikey_counter(tok.ctr) << 8) | tok.use; if (ctr <= last_ctr) { syslog(LOG_INFO, "user %s: counter %u.%u <= %u.%u " "(REPLAY ATTACK!)", username, ctr / 256, ctr % 256, last_ctr / 256, last_ctr % 256); return (AUTH_FAILED); } syslog(LOG_INFO, "user %s: counter %u.%u > %u.%u", username, ctr / 256, ctr % 256, last_ctr / 256, last_ctr % 256); umask(S_IRWXO); if ((f = fopen(fn, "w")) == NULL) { syslog(LOG_ERR, "user %s: fopen: %s: %m", username, fn); return (AUTH_FAILED); } fprintf(f, "%u", ctr); fclose(f); return (AUTH_OK); }
/* * Authenticate the user with the given password. */ static rlm_rcode_t mod_authenticate(void *instance, REQUEST *request) { rlm_yubikey_t *inst = instance; char *passcode; size_t i, len; uint32_t counter; const DICT_ATTR *da; VALUE_PAIR *key, *vp; yubikey_token_st token; char private_id[(YUBIKEY_UID_SIZE * 2) + 1]; /* * Can't do yubikey auth if there's no password. */ if (!request->password || (request->password->da->attr != PW_USER_PASSWORD)) { RDEBUGE("No Clear-Text password in the request. Can't do Yubikey authentication."); return RLM_MODULE_FAIL; } passcode = request->password->vp_strvalue; len = request->password->length; /* * Verify the passcode is the correct length (in its raw * modhex encoded form). * * <public_id (6-16 bytes)> + <aes-block (32 bytes)> */ if (len != (inst->id_len + 32)) { RDEBUGE("User-Password value is not the correct length, expected %u, got %zu", inst->id_len + 32, len); return RLM_MODULE_FAIL; } for (i = inst->id_len; i < len; i++) { if (!is_modhex(*passcode)) { RDEBUG2("User-Password (aes-block) value contains non modhex chars"); return RLM_MODULE_FAIL; } } da = dict_attrbyname("Yubikey-Key"); key = pairfind(request->config_items, da->attr, da->vendor, TAG_ANY); if (!key) { RDEBUGE("Yubikey-Key attribute not found in control list, can't decrypt OTP data"); return RLM_MODULE_FAIL; } if (key->length != YUBIKEY_KEY_SIZE) { RDEBUGE("Yubikey-Key length incorrect, expected %u got %zu", YUBIKEY_KEY_SIZE, key->length); return RLM_MODULE_FAIL; } yubikey_parse(request->password->vp_octets + inst->id_len, key->vp_octets, &token); /* * Apparently this just uses byte offsets... */ if (!yubikey_crc_ok_p((uint8_t *) &token)) { RDEBUGE("Decrypting OTP token data failed, rejecting"); return RLM_MODULE_REJECT; } RDEBUG("Token data decrypted successfully"); if (request->options && request->radlog) { (void) fr_bin2hex((uint8_t*) &token.uid, (char *) &private_id, YUBIKEY_UID_SIZE); RDEBUG2("Private ID : 0x%s", private_id); RDEBUG2("Session counter : %u", yubikey_counter(token.ctr)); RDEBUG2("# used in session : %u", token.use); RDEBUG2("Token timetamp : %u", (token.tstph << 16) | token.tstpl); RDEBUG2("Random data : %u", token.rnd); RDEBUG2("CRC data : 0x%x", token.crc); } /* * Private ID used for validation purposes */ vp = pairmake(request, &request->packet->vps, "Yubikey-Private-ID", NULL, T_OP_SET); memcpy(vp->vp_octets, token.uid, YUBIKEY_UID_SIZE); vp->length = YUBIKEY_UID_SIZE; /* * Token timestamp */ vp = pairmake(request, &request->packet->vps, "Yubikey-Timestamp", NULL, T_OP_SET); vp->vp_integer = (token.tstph << 16) | token.tstpl; vp->length = 4; /* * Token random */ vp = pairmake(request, &request->packet->vps, "Yubikey-Random", NULL, T_OP_SET); vp->vp_integer = token.rnd; vp->length = 4; /* * Combine the two counter fields together so we can do * replay attack checks. */ counter = (yubikey_counter(token.ctr) << 16) | token.use; vp = pairmake(request, &request->packet->vps, "Yubikey-Counter", NULL, T_OP_SET); vp->vp_integer = counter; vp->length = 4; /* * Now we check for replay attacks */ vp = pairfind(request->config_items, vp->da->attr, vp->da->vendor, TAG_ANY); if (!vp) { RDEBUGW("Yubikey-Counter not found in control list, skipping replay attack checks"); return RLM_MODULE_OK; } if (counter <= vp->vp_integer) { RDEBUGE("Replay attack detected! Counter value %u, is lt or eq to last known counter value %u", counter, vp->vp_integer); return RLM_MODULE_REJECT; } return RLM_MODULE_OK; }