/* * Copyright (c) 2005 Mark Fullmer * Copyright (c) 2009 Mark Fullmer and the Ohio State University * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $Id: otplib.c 18 2009-11-26 19:40:06Z maf $ */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "otplib.h" #include "xerr.h" #include "ffdb.h" #include "str.h" #include "otpsc.h" char *otp_l_status[] = {"error", "active", "inactive", "disabled"}; char *otp_l_format[] = {"error", "hex40"}; char *otp_l_type[] = {"error", "HOTP"}; /* * One Time Password library with HOTP implementation. * * **** * * otp_ou_toascii() struct otp_user to ASCII * otp_ou_fromascii ASCII to struct otp_user * **** * * otp_hotp_hex40_auth() HOTP 40 bit hex key authentication * otp_hotp_hex40_crsp() HOTP 40 bit hex key challenge response generator * **** * * otp_db_open() open OTP db * otp_db_close() close OTP db * otp_db_valid() OTP db pointer valid? * otp_db_dump() dump OTP db to ASCII * otp_db_load() load OTP db from ASCII * **** * * otp_user_add() add user to OTP db * otp_user_exists() user exists in OTP db? * otp_user_rm() remove user from OTP db * otp_user_auth() authenticate user from OTP db * **** * * otp_urec_open() open OTP db user record * otp_urec_close() close OTP db user record * otp_urec_get() get/read OTP db user record (after open) * otp_urec_put() put/write OTP db user record (after open) * otp_urec_sanity() check sanity of OTP user db record * otp_urec_crsp() generate challenge response for OTP user db record * otp_urec_disp() display/printable output of OTP user db record * otp_urec_dispsc() smart card friendly output of user db record * */ /* * function: otp_ou_toascii() * * convert binary ou struct to : separated ASCII string. ASCII * string is stored in the context. * * arguments: * otpctx - otp context from otp_db_open() * ou - otp_user struct source * * returns: <0 : fail * 0 : success * */ int otp_ou_toascii(struct otp_ctx *otpctx, struct otp_user *ou) { char *c; int n; if (otp_db_valid(otpctx, "otp_ou_toascii") < 0) return -1; #if BYTE_ORDER == LITTLE_ENDIAN SWAP64(ou->last) SWAP64(ou->count) SWAP64(ou->count_ceil) #endif /* BYTE_ORDER */ /* * encode to: * version:name:key:status:format:type:count:count_ceil:last */ c = ou->ascii_encoded; str_hex_dump(c, (void*)&ou->version, 1); c += 2; if ((n = strlen(ou->username)) > OTP_USER_NAME_LEN) { xerr_warnx("otp_ou_toascii(): username length invalid."); return -1; } *c++ = ':'; strcpy(c, ou->username); c += n; *c++ = ':'; str_hex_dump(c, ou->key, 20); c += 40; *c++ = ':'; str_hex_dump(c, (void*)&ou->status, 1); c += 2; *c++ = ':'; str_hex_dump(c, (void*)&ou->format, 1); c += 2; *c++ = ':'; str_hex_dump(c, (void*)&ou->type, 1); c += 2; *c++ = ':'; str_hex_dump(c, (void*)&ou->flags, 1); c += 2; *c++ = ':'; str_hex_dump(c, (void*)&ou->count, 8); c += 16; *c++ = ':'; str_hex_dump(c, (void*)&ou->count_ceil, 8); c += 16; *c++ = ':'; str_hex_dump(c, (void*)&ou->last, 8); c += 16; *c++ = 0; #if BYTE_ORDER == LITTLE_ENDIAN SWAP64(ou->last) SWAP64(ou->count) SWAP64(ou->count_ceil) #endif /* BYTE_ORDER */ ou->db_val.val = &ou->ascii_encoded; ou->db_val.size = strlen(ou->ascii_encoded); return 0; } /* otp_ou_toascii */ /* * function: otp_ou_fromascii() * * convert ASCII : separated user record in otpctx to binary ou * * arguments: * otpctx - otp context from otp_db_open() * ou - otp_user struct filled in * * returns: <0 : fail * 0 : success * * */ int otp_ou_fromascii(struct otp_ctx *otpctx, struct otp_user *ou) { int field, ret, n, i; char *c; if (otp_db_valid(otpctx, "otp_ou_fromascii") < 0) return -1; /* strip \n */ for (i = 0; i < OTP_USER_ASCII_LEN; ++i) { if (ou->ascii_encoded[i] == '\n') { ou->ascii_encoded[i] = 0; break; } /* if */ if (ou->ascii_encoded[i] == 0) break; } /* for */ ret = OTP_ERROR; /* first pass, sanity check : verify n fields, replace : with 0 */ field = 0; c = ou->ascii_encoded; for (;;) { if (*c == ':') { *c = 0; ++c; ++field; if (field > (OTP_USER_N_FIELDS-1)) /* too many fields */ break; } else if (*c == 0) { break; } else { ++c; } } if (field != (OTP_USER_N_FIELDS-1)) { if (otpctx->verbose) xerr_warnx("expecting %d fields, got %d.", OTP_USER_N_FIELDS, field+1); return -1; } #define CHK_STRLEN(N,L)\ n = strlen(c);\ if (n != L) {\ if (otpctx->verbose)\ xerr_warnx("%s: %s length expecting %d, got %d.", __func__, N, L, n);\ goto otp_ou_fromascii_out;\ }\ #define CHK_STRRANGE(N,L,H)\ n = strlen(c);\ if ((n > H) || (n < L)) {\ if (otpctx->verbose)\ xerr_warnx("%s: %s length range (%d,%d), got %d.", __func__, N, L, H, n);\ goto otp_ou_fromascii_out;\ }\ #define HEX_DECODE(N,V,E)\ if (str_hex_decode(c, E, (u_char*)&ou->V, E>>1) < 0) {\ if (otpctx->verbose)\ xerr_warnx("%s: str_hex_decode(%s): failed.", __func__, N);\ goto otp_ou_fromascii_out;\ }\ for (field = 0, c = ou->ascii_encoded; field < OTP_USER_N_FIELDS; ++field) { if (field == 0) { /* version */ CHK_STRLEN("version", 2) HEX_DECODE("version", version, 2) if ((ou->version < OTP_VERSION_MIN) || (ou->version > OTP_VERSION_MAX)) { if (otpctx->verbose) xerr_warnx("version not in range (%d,%d).", OTP_VERSION_MIN, OTP_VERSION_MAX); return -1; } } else if (field == 1) { /* username */ CHK_STRRANGE("username", 1, 32) strcpy(ou->username, c); } else if (field == 2) { /* key */ CHK_STRRANGE("key", 1, 40); HEX_DECODE("key", key, 40) } else if (field == 3) { /* status */ CHK_STRLEN("status", 2); HEX_DECODE("status", status, 2) } else if (field == 4) { /* format */ CHK_STRLEN("format", 2); HEX_DECODE("format", format, 2) } else if (field == 5) { /* type */ CHK_STRLEN("type", 2); HEX_DECODE("type", type, 2) } else if (field == 6) { /* flags */ CHK_STRLEN("flags", 2); HEX_DECODE("flags", flags, 2) } else if (field == 7) { /* count */ CHK_STRLEN("count", 16); HEX_DECODE("count", count, 16) } else if (field == 8) { /* count_ceil */ CHK_STRLEN("count_ceil", 16); HEX_DECODE("count_ceil", count_ceil, 16) } else if (field == 9) { /* last */ CHK_STRLEN("last", 16); HEX_DECODE("last", last, 16) } /* skip to next field */ for (; *c; ++c); ++c; } /* foreach field */ #if BYTE_ORDER == LITTLE_ENDIAN SWAP64(ou->last) SWAP64(ou->count) SWAP64(ou->count_ceil) #endif /* BYTE_ORDER */ ou->key_size = OTP_HOTP_KEY_SIZE; ou->db_key.key = &ou->username; ou->db_key.size = strlen(ou->username); ret = OTP_SUCCESS; otp_ou_fromascii_out: return ret; } /* otp_ou_fromascii */ /* * function: otp_hotp_hex40_auth() * * validate challenge HOTP 40 bit hex format challenge response * for user ou with window. * * arguments: * ou - otp user struct * crsp - user response * window - window of challenge responses to attempt * * return: OTP_ERROR - error * OTP_AUTH_PASS - user authenticated * OTP_AUTH_FAIL - user not authenticated * */ int otp_hotp_hex40_auth(struct otp_ctx *otpctx, struct otp_user *ou, char *crsp, int window) { uint64_t tmp_count; u_int rlen; int i; u_char result[EVP_MAX_MD_SIZE], decoded[5]; if (otp_db_valid(otpctx, "otp_hotp_hex40_auth") < 0) return -1; if (strlen(crsp) != 10) { if (otpctx->verbose) xerr_warnx("strlen(crsp) != 10."); return OTP_AUTH_FAIL; } /* expecting at most 10 hex digits 5*8=40 bits */ if (str_hex_decode(crsp, 10, decoded, 5) < 0) { if (otpctx->verbose) xerr_warnx("str_hex_decode(): failed."); return OTP_AUTH_FAIL; } tmp_count = ou->count; /* try to authenticate with count, incrementing count up to count+window */ for (i = 0; i < window; ++i, ++tmp_count) { /* HOTP is big endian */ #if BYTE_ORDER == LITTLE_ENDIAN SWAP64(tmp_count) #endif /* BYTE_ORDER */ /* compute expected response to challenge */ if (!HMAC(EVP_sha1(), ou->key, 20, (void*)&tmp_count, 8, result, &rlen)) { if (otpctx->verbose) xerr_warnx("HMAC(): failed."); return OTP_ERROR; } /* restore from HOTP standard byte order */ #if BYTE_ORDER == LITTLE_ENDIAN SWAP64(tmp_count) #endif /* BYTE_ORDER */ /* compare the top 40 bits to authenticate user, match then return good */ if (!bcmp(decoded, &result, 5)) { ou->count = tmp_count+1; return OTP_AUTH_PASS; } } return OTP_AUTH_FAIL; } /* otp_hotp_hex40_auth */ /* * function: otp_hotp_hex40_crsp() * * generate HOTP challenge response in hex40 format from data in ou * with optional * count_offset applied. Store results in buf as * null terminated ASCII string. * * arguments: * otpctx - otp context from otp_db_open() * ou - otp_user struct source * count_offset - offset of count from current count in ou * buf - buffer with ASCII result. Min 11 bytes. * * returns: <0 : fail * 0 : success * */ int otp_hotp_hex40_crsp(struct otp_ctx *otpctx, struct otp_user *ou, int64_t count_offset, char *buf, size_t buf_size) { uint64_t tmp_count; u_char result[EVP_MAX_MD_SIZE]; u_int rlen; if (otp_db_valid(otpctx, "otp_hotp_hex40_crsp") < 0) return -1; /* HOTP is big endian */ tmp_count = ou->count; tmp_count += count_offset; #if BYTE_ORDER == LITTLE_ENDIAN SWAP64(tmp_count) #endif /* BYTE_ORDER */ /* compute expected response to challenge */ if (!HMAC(EVP_sha1(), ou->key, 20, (void*)&tmp_count, 8, result, &rlen)) { if (otpctx->verbose) xerr_warnx("HMAC(): failed."); return OTP_ERROR; } /* two bytes for each digit + null terminator */ if (buf_size < 11) { xerr_warnx("buf_size < 11"); return OTP_ERROR; } str_hex_dump(buf, result, 5); return 0; } /* otp_hotp_hex40_crsp */ /* * function: otp_db_open() * * open OTP user database, return db context * otp_db_close() must be called to resources by open db context * * arguments: * dbname - pathname to db * flags - OTP_DB_* * OTP_DB_VERBOSE - enable verbose warnings and errors * OTP_DB_CREATE - create db if it does not exist * OTP_DB_CREATE_SOFT - fail quiet if db exists and OTP_DB_CREATE set * * returns: 0L failure * struct otp_ctx success * */ struct otp_ctx *otp_db_open(char *dbname, int flags) { struct otp_ctx *otpctx; int ret, verbose, ffdb_flags; ffdb_flags = 0; otpctx = (void*)0L; ret = -1; if (flags & OTP_DB_VERBOSE) { verbose = 1; ffdb_flags |= FFDB_DB_VERBOSE; } else { verbose = 0; } if (flags & OTP_DB_CREATE) ffdb_flags |= FFDB_DB_CREATE; if (flags & OTP_DB_CREATE_SOFT) ffdb_flags |= FFDB_DB_CREATE_SOFT; if (!(otpctx = (struct otp_ctx*)malloc(sizeof *otpctx))) { if (verbose) xerr_warn("malloc(otpctx): failed."); goto otp_db_open_out; } bzero(otpctx, sizeof *otpctx); otpctx->verbose = verbose; if (!(otpctx->ffdbctx = ffdb_db_open(dbname, OTP_USER_NAME_LEN, OTP_USER_ASCII_LEN, ffdb_flags, S_IRUSR|S_IWUSR, S_IRWXU))) { if (verbose) xerr_warnx("ffdb_db_open(): failed."); goto otp_db_open_out; } otpctx->valid = 1; ret = 0; /* success */ otp_db_open_out: if ((ret == -1) && (otpctx) && (otpctx->ffdbctx)) ffdb_db_close(otpctx->ffdbctx); if ((ret == -1) && (otpctx)) { bzero(otpctx, sizeof *otpctx); free(otpctx); otpctx = (void*)0L; } return otpctx; } /* otp_db_open */ /* * function: otp_db_close() * * close db context created by otp_db_open() * * arguments: * otpctx - otp db context returned by otp_db_open() * * returns: <0 failure * 0 success * */ int otp_db_close(struct otp_ctx *otpctx) { if (otp_db_valid(otpctx, "otp_db_close") == -1) return -1; if (ffdb_db_close(otpctx->ffdbctx) == -1) return -1; bzero(otpctx, sizeof *otpctx); free (otpctx); return 0; } /* otp_db_close */ /* * function: otp_db_valid() * * simple validation of otpctx * * arguments: * otpctx - otp db context returned by otp_db_open() * * returns: <0 failure * 0 success */ int otp_db_valid(struct otp_ctx *otpctx, char *who) { if (!otpctx) { xerr_warnx("%s(): fatal, no context.", who); return -1; } if (!otpctx->valid) { if (otpctx->verbose) xerr_warnx("%s(): fatal, invalid context.", who); return -1; } return 0; } /* otp_db_valid */ /* * function otp_db_dump() * * Dump database in ASCII to stdout * * arguments: * otpctx - otp db context returned by otp_db_open() * u_username - optional username. null to dump all users * * returns: <0 failure * 0 success */ int otp_db_dump(struct otp_ctx *otpctx, char *u_username) { struct ffdb_key db_key; struct ffdb_val db_val; int r, ret, ul; char buf[OTP_USER_ASCII_LEN+1]; if (otp_db_valid(otpctx, "otp_db_dump") < 0) return -1; ret = -1; if (u_username) ul = strlen(u_username); else ul = 0; printf("#version:user:key:status:format:type:flags:count_cur:count_ceil:last\n"); /* first record */ r = ffdb_rec_iter(otpctx->ffdbctx, &db_key, &db_val, FFDB_ITER_FIRST|FFDB_ITER_GET); while (1) { if (r < 0) { if (otpctx->verbose) xerr_warnx("ffdb_rec_iter(): failed."); goto otp_db_dump_out; } /* last? */ if (r == 1) { ret = 0; break; } /* match on single username? */ if (ul) { /* not same strlen? */ if (db_key.size != ul) goto otp_db_dump_skip1; /* not same string? */ if (bcmp(db_key.key, u_username, ul)) goto otp_db_dump_skip1; } if (db_val.size > OTP_USER_ASCII_LEN) { if (otpctx->verbose) xerr_warnx("db_val.size > OTP_USER_ASCII_LEN."); goto otp_db_dump_out; } bcopy(db_val.val, buf, db_val.size); buf[db_val.size] = 0; printf("%s\n", buf); otp_db_dump_skip1: /* next record */ r = ffdb_rec_iter(otpctx->ffdbctx, &db_key, &db_val, FFDB_ITER_GET); } ret = 0; /* success */ otp_db_dump_out: return ret; } /* otp_db_dump */ /* * function: otp_db_load() * * load otp db from stdin. * * arguments: * otpctx - otp db context returned by otp_db_open() * u_username - optional username. null to load all users * * returns: <0 failure * 0 success */ int otp_db_load(struct otp_ctx *otpctx, char *u_username) { struct otp_user ou; int ret, l, i; char buf[OTP_USER_ASCII_LEN<<2]; char *c; if (otp_db_valid(otpctx, "otp_db_load") < 0) return -1; ret = -1; /* fail */ while (!feof(stdin)) { /* get line or EOF */ if (!fgets(buf, (OTP_USER_ASCII_LEN<<2), stdin)) break; /* skip whitespace */ for (c = buf; *c && ((*c == ' ') || (*c == '\t')); ++c); /* skip # comment lines */ if (*c == '#') continue; l = strlen(c); /* sanity check line length */ if (l > OTP_USER_ASCII_LEN) { if (otpctx->verbose) xerr_warnx("line buf > OTP_USER_ASCII_LEN."); goto otp_db_load_out; } /* copy line into user data struct */ bcopy(c, &ou.ascii_encoded, l); /* ASCII to binary / sanity checking */ if (otp_ou_fromascii(otpctx, &ou) < 0) { if (otpctx->verbose) xerr_warnx("otp_ou_fromascii(): failed."); goto otp_db_load_out; } /* limit to single username load? */ if (u_username && (strcmp(u_username, ou.username))) { if (otpctx->verbose) printf("skip %s\n", ou.username); continue; } /* strip \n on input */ for (i = 0; i < l; ++i) { if (buf[i] == '\n') { buf[i] = 0; break; } } if (otpctx->verbose) printf("load %s\n", buf); /* binary to ASCII / sanity checking */ if (otp_ou_toascii(otpctx, &ou) < 0) { if (otpctx->verbose) xerr_warnx("otp_ou_toascii(): failed."); goto otp_db_load_out; } /* open/create user record */ if (ffdb_rec_open(otpctx->ffdbctx, &ou.db_key, O_RDWR|O_CREAT, FFDB_OP_LOCK_EX) < 0) { if (otpctx->verbose) xerr_warnx("ffdb_rec_open(): failed."); goto otp_db_load_out; } /* store record */ if (ffdb_rec_put(otpctx->ffdbctx, &ou.db_key, &ou.db_val, 0) < 0) { if (otpctx->verbose) xerr_warnx("ffdb_rec_put(): failed."); goto otp_db_load_out; } /* close record */ if (ffdb_rec_close(otpctx->ffdbctx, &ou.db_key) < 0) { if (otpctx->verbose) xerr_warnx("ffdb_rec_close(): failed."); goto otp_db_load_out; } } /* while */ ret = 0; /* success */ otp_db_load_out: return ret; } /* otp_db_load */ /* * function: otp_user_add() * * add user u_username to otpdb * * arguments: * otpctx - otp db context returned by otp_db_open() * u_username - username * u_key_val - key value * u_key_size - length of key in bytes * u_count - initial count * u_count_ceil - count ceiling * u_status - status OTP_STATUS_* * u_type - type OTP_TYPE_HOTP (HOTP implemented) * u_format - format OTP_FORMAT_HEX40 (HEX40 implemented) * u_version - version OTP_VERSION (version 1 implemented) * * * returns: <0 failure * 0 success * */ int otp_user_add(struct otp_ctx *otpctx, char *u_username, uint8_t *u_key_val, uint16_t u_key_size, uint64_t u_count, uint64_t u_count_ceil, uint8_t u_status, uint8_t u_type, uint8_t u_format, uint8_t u_version) { struct otp_user ou; int ret, r; if (otp_db_valid(otpctx, "otp_user_add") < 0) return -1; ret = -1; /* fail */ bzero(&ou, sizeof ou); ou.db_key.key = u_username; ou.db_key.size = strlen(u_username); /* * sanity checks */ if (ou.db_key.size > OTP_USER_NAME_LEN) { if (otpctx->verbose) xerr_warnx("strlen(u_username) > OTP_USER_NAME_LEN."); goto otp_user_add_out; } if (u_key_size > OTP_USER_KEY_LEN) { if (otpctx->verbose) xerr_warnx("key_size > OTP_USER_KEY_LEN."); goto otp_user_add_out; } /* * copy in user fields to ou */ strcpy(ou.username, u_username); bcopy(u_key_val, &ou.key, u_key_size); ou.key_size = u_key_size; ou.count = u_count; ou.count_ceil = u_count_ceil; ou.last = 0; ou.version = u_version; ou.status = u_status; ou.format = u_format; ou.type = u_type; if (otp_urec_sanity(otpctx, &ou) < 0) { if (otpctx->verbose) xerr_warnx("otp_urec_sanity(): failed."); goto otp_user_add_out; } /* does user exist? */ r = ffdb_rec_exists(otpctx->ffdbctx, &ou.db_key); /* yes */ if (r == 0) { if (otpctx->verbose) xerr_warnx("user %s exists in otp db, fail.", u_username); goto otp_user_add_out; } /* ffdb_rec_exists failure */ if (r < 0) { if (otpctx->verbose) xerr_warnx("ffdb_rec_exists(): failed."); goto otp_user_add_out; } /* user struct binary to ASCII */ if (otp_ou_toascii(otpctx, &ou) < 0) { if (otpctx->verbose) xerr_warnx("otp_ou_toascii(): failed."); goto otp_user_add_out; } /* open/create user record */ if (ffdb_rec_open(otpctx->ffdbctx, &ou.db_key, O_RDWR|O_CREAT, FFDB_OP_LOCK_EX) < 0) { if (otpctx->verbose) xerr_warnx("ffdb_rec_open(): failed."); goto otp_user_add_out; } /* store user record */ if (ffdb_rec_put(otpctx->ffdbctx, &ou.db_key, &ou.db_val, 0) < 0) { if (otpctx->verbose) xerr_warnx("ffdb_rec_put(): failed."); goto otp_user_add_out; } /* close user record */ if (ffdb_rec_close(otpctx->ffdbctx, &ou.db_key) < 0) { if (otpctx->verbose) xerr_warnx("ffdb_rec_close(): failed."); goto otp_user_add_out; } ret = 0; /* success */ otp_user_add_out: return ret; } /* otp_user_add */ /* * function: otp_user_exists() * * test if user exists in otp db * * arguments: * otpctx - otp db context returned by otp_db_open() * u_username - username * * returns: <0 failure * 0 success * 1 key does not exist */ int otp_user_exists(struct otp_ctx *otpctx, char *u_username) { struct ffdb_key db_key; int ret; if (otp_db_valid(otpctx, "otp_user_exists") < 0) return -1; /* paranoia */ str_safe(u_username, OTP_USER_NAME_LEN); ret = OTP_ERROR; /* fail */ db_key.key = u_username; db_key.size = strlen(u_username); /* * sanity checks */ if (db_key.size > OTP_USER_NAME_LEN) { if (otpctx->verbose) xerr_warnx("strlen(u_username) > OTP_USER_NAME_LEN."); goto otp_user_exists_out; } /* does user exist? */ ret = ffdb_rec_exists(otpctx->ffdbctx, &db_key); /* ffdb_rec_exists failure */ if (ret < 0) if (otpctx->verbose) xerr_warnx("ffdb_rec_exists(): failed."); otp_user_exists_out: return ret; } /* otp_user_exists */ /* * function: otp_user_rm() * * remove user from otp database * * arguments: * otpctx - otp db context returned by otp_db_open() * u_username - username * * returns: <0 failure * 0 success * */ int otp_user_rm(struct otp_ctx *otpctx, char *u_username) { struct ffdb_key db_key; int ret; if (otp_db_valid(otpctx, "otp_user_rm") < 0) return -1; /* paranoia */ str_safe(u_username, OTP_USER_NAME_LEN); ret = -1; /* fail */ db_key.key = u_username; db_key.size = strlen(u_username); /* * sanity checks */ if (db_key.size > OTP_USER_NAME_LEN) { if (otpctx->verbose) xerr_warnx("strlen(u_username) > OTP_USER_NAME_LEN."); goto otp_user_rm_out; } /* remove user */ ret = ffdb_rec_rm(otpctx->ffdbctx, &db_key); /* ffdb_rec_rm failure */ if (ret < 0) if (otpctx->verbose) xerr_warnx("ffdb_rec_rm(): failed."); otp_user_rm_out: return ret; } /* otp_user_rm */ /* * otp_user_auth() * * returns: <0 failure * OTP_AUTH_FAIL - user not authenticated * database update * authenticate attempt time * OTP_AUTH_PASS - user authenticated, database updated with * database update * authenticate attempt time * new count */ int otp_user_auth(struct otp_ctx *otpctx, char *u_username, char *u_crsp, int u_window) { time_t now; struct otp_user ou; int ret, r, auth_status; if (otp_db_valid(otpctx, "otp_user_auth") < 0) return -1; /* paranoia */ str_safe(u_username, OTP_USER_NAME_LEN); ret = -1; /* fail */ bzero(&ou, sizeof ou); auth_status = OTP_AUTH_FAIL; /* paranoia */ str_safe(u_username, OTP_USER_NAME_LEN); str_safe(u_crsp, OTP_HOTP_HEX40_LEN<<1); /* open user record */ if (otp_urec_open(otpctx, u_username, &ou, O_RDWR, FFDB_OP_LOCK_EX) < 0) { if (otpctx->verbose) xerr_warnx("otp_urec_open(%s): failed.", u_username); goto otp_user_auth_out; } /* get user record */ if (otp_urec_get(otpctx, &ou) < 0) { if (otpctx->verbose) xerr_warnx("otp_urec_get(%s): failed.", u_username); goto otp_user_auth_out; } now = time((time_t)0L); /* only allow 1 try per second for this user */ if (now == ou.last) { ret = OTP_AUTH_FAIL; /* soft fail */ if (otpctx->verbose) xerr_warnx("User %s exceeded otp auth policer.", u_username); goto otp_user_auth_out; } if (otp_urec_sanity(otpctx, &ou) < 0) { if (otpctx->verbose) xerr_warnx("otp_urec_sanity(): failed."); goto otp_user_auth_out; } /* set for next time */ ou.last = now; /* try to authenticate user */ if (ou.status != OTP_STATUS_ACTIVE) auth_status = OTP_AUTH_FAIL; else if (ou.count >= ou.count_ceil) auth_status = OTP_AUTH_FAIL; else auth_status = otp_hotp_hex40_auth(otpctx, &ou, u_crsp, u_window); /* * regardless of authentication status update the db to reflect last access */ if (otp_urec_put(otpctx, &ou) < 0) { if (otpctx->verbose) xerr_warnx("otp_urec_put(): failed."); goto otp_user_auth_out; } /* set return code to auth status */ ret = auth_status; otp_user_auth_out: /* close record */ if (ou.db_key.fd && (ou.db_key.fd != -1)) { if ((r = otp_urec_close(otpctx, &ou)) < 0) { if (otpctx->verbose) xerr_warnx("otp_urec_close(): failed."); ret = r; } } return ret; } /* otp_user_auth */ /* * function: otp_urec_open() * * open an otp user record in otp database. A user record must * be closed with otp_urec_close() to release resources allocated * during the otp_urec_open(). A user can be created with * open_flags = O_RDWR|OCREAT. For read only records use O_RDONLY. * The record can be locked by setting appropriate flags in op_flags * * examples: * * ;;;; create new user test, hold exclusive lock on record * otp_urec_open(otpctx, "test", &ou, O_RDWR|O_CREAT, FFDB_OP_LOCK_EX) * ; fill in ou * otp_urec_put(otpctx, &ou) * otp_urec_close(otpctx, &ou) * * ;;;; read/modify/write user test, hold exclusive lock on record * otp_urec_open(otpctx, "test", &ou, O_RDWR, FFDB_OP_LOCK_EX) * otp_urec_get(otpctx, &ou) * ; modify ou fields * otp_urec_put(otpctx, &ou) * otp_urec_close(otpctx, &ou) * * ;;;; display a user record, get shared lock * otp_urec_open(otpctx, "test", &ou, O_RDONLY, FFDB_OP_LOCK_SH) * otp_urec_get(otpctx, &ou) * otp_urec_disp(otpctx, &ou) * otp_urec_close(otpctx, &ou) * * arguments: * otpctx - otp db context returned by otp_db_open() * u_username - username * ou - otp_user record * open_flags - flags passed to open(2) * op_flags - flags FFDB_OP_* * * returns: <0 failure * 0 success * */ int otp_urec_open(struct otp_ctx *otpctx, char *u_username, struct otp_user *ou, int open_flags, int op_flags) { int ret; if (otp_db_valid(otpctx, "otp_urec_open") < 0) return -1; /* paranoia */ str_safe(u_username, OTP_USER_NAME_LEN); ret = -1; /* fail */ bzero(ou, sizeof *ou); ou->db_key.fd = -1; /* invalid */ ou->db_key.key = u_username; ou->db_key.size = strlen(u_username); /* * sanity checks */ if (ou->db_key.size > OTP_USER_NAME_LEN) { if (otpctx->verbose) xerr_warnx("strlen(u_username) > OTP_USER_NAME_LEN."); goto otp_urec_open_out; } /* open user record */ if (ffdb_rec_open(otpctx->ffdbctx, &ou->db_key, open_flags, op_flags) < 0) { if (otpctx->verbose) xerr_warnx("ffdb_rec_open(): failed."); goto otp_urec_open_out; } ret = 0; /* success */ otp_urec_open_out: return ret; } /* otp_urec_open */ /* * otp_urec_close() * * close user record opened with otp_urec_open() * * arguments: * otpctx - otp db context returned by otp_db_open() * ou - otp_user record opened with otp_urec_open() * * returns: <0 failure * 0 success * */ int otp_urec_close(struct otp_ctx *otpctx, struct otp_user *ou) { if (otp_db_valid(otpctx, "otp_urec_close") < 0) return -1; if (ffdb_rec_close(otpctx->ffdbctx, &ou->db_key) < 0) { if (otpctx->verbose) xerr_warnx("ffdb_rec_close(): failed."); return -1; } return 0; /* success */ } /* otp_urec_close */ /* * function: otp_urec_get() * * read otp user db record opened with otp_urec_open() * * arguments: * otpctx - otp db context returned by otp_db_open() * ou - otp_user record opened with otp_urec_open() * * returns: <0 failure * 0 success * */ int otp_urec_get(struct otp_ctx *otpctx, struct otp_user *ou) { int ret; if (otp_db_valid(otpctx, "otp_urec_get") < 0) return -1; ret = -1; /* fail */ /* get user record */ if (ffdb_rec_get(otpctx->ffdbctx, &ou->db_key, &ou->db_val, 0) < 0) { if (otpctx->verbose) xerr_warnx("ffdb_rec_get(%s): failed.", ou->username); goto otp_urec_get_out; } /* sanity check size */ if (ou->db_val.size > OTP_USER_ASCII_LEN) { if (otpctx->verbose) xerr_warnx("db_val.size > OTP_USER_ASCII_LEN."); goto otp_urec_get_out; } /* copy out */ bcopy(ou->db_val.val, ou->ascii_encoded, ou->db_val.size); /* null terminate */ ou->ascii_encoded[ou->db_val.size] = 0; /* ASCII to binary */ if (otp_ou_fromascii(otpctx, ou) < 0) { if (otpctx->verbose) xerr_warnx("otp_ou_fromascii(): failed."); goto otp_urec_get_out; } ret = 0; /* success */ otp_urec_get_out: return ret; } /* otp_urec_get */ /* * function: otp_urec_put() * * write otp user db record opened with otp_urec_open() * * arguments: * otpctx - otp db context returned by otp_db_open() * ou - otp_user record opened with otp_urec_open() * * returns: <0 failure * 0 success * */ int otp_urec_put(struct otp_ctx *otpctx, struct otp_user *ou) { int ret; if (otp_db_valid(otpctx, "otp_urec_put") < 0) return -1; ret = -1; /* fail */ /* convert binary struct to ASCII for db */ if (otp_ou_toascii(otpctx, ou) < 0) { if (otpctx->verbose) xerr_warnx("otp_ou_toascii(): failed."); goto otp_urec_put_out; } /* update db */ if (ffdb_rec_put(otpctx->ffdbctx, &ou->db_key, &ou->db_val, 0) < 0) { if (otpctx->verbose) xerr_warnx("ffdb_rec_put(%s): failed.", ou->username); goto otp_urec_put_out; } ret = 0; /* success */ otp_urec_put_out: return ret; } /* otp_urec_put */ /* * function: otp_urec_sanity() * * perform sanity checks on otp_user structure. Used internally * with otp_urec_*() functions before performing operations on * structure. * * arguments: * otpctx - otp db context returned by otp_db_open() * ou - otp_user record opened with otp_urec_open() * * returns: <0 failure * 0 success * */ int otp_urec_sanity(struct otp_ctx *otpctx, struct otp_user *ou) { if (otp_db_valid(otpctx, "otp_urec_sanity") < 0) return -1; if (ou->type != OTP_TYPE_HOTP) { if (otpctx->verbose) xerr_warnx("type != OTP_TYPE_HOTP."); return -1; } if (ou->format != OTP_FORMAT_HEX40) { if (otpctx->verbose) xerr_warnx("format != OTP_FORMAT_HEX40."); return -1; } if (ou->version != OTP_VERSION) { if (otpctx->verbose) xerr_warnx("version != OTP_VERSION."); return -1; } if (ou->key_size != OTP_HOTP_KEY_SIZE) { if (otpctx->verbose) xerr_warnx("key_size != OTP_HOTP_KEY_SIZE."); return -1; } return 0; /* success */ } /* otp_urec_sanity */ /* * function: otp_urec_crsp() * * generate challenge response for ou * HOTP HEX40 implemented. * * arguments: * otpctx - otp db context returned by otp_db_open() * ou - otp_user record opened with otp_urec_open() * * returns: <0 failure * 0 success * */ int otp_urec_crsp(struct otp_ctx *otpctx, struct otp_user *ou, int64_t count_offset, char *buf, size_t buf_size) { if (otp_db_valid(otpctx, "otp_urec_crsp") < 0) return -1; if (buf_size < 5) { if (otpctx->verbose) xerr_warnx("buf_size < 5."); goto otp_urec_crsp_out; } if (otp_urec_sanity(otpctx, ou) < 0) { if (otpctx->verbose) xerr_warnx("otp_urec_sanity(): failed."); goto otp_urec_crsp_out; } return (otp_hotp_hex40_crsp(otpctx, ou, count_offset, buf, buf_size)); otp_urec_crsp_out: return -1; } /* otp_urec_crsp */ /* * function: otp_urec_disp() * * format and display ou to stdout * * arguments: * otpctx - otp db context returned by otp_db_open() * ou - otp_user record opened with otp_urec_open() * * returns: <0 failure * 0 success * */ void otp_urec_disp(struct otp_ctx *otpctx, struct otp_user *ou) { char tmp[41]; if (otp_db_valid(otpctx, "otp_urec_disp") < 0) return; str_hex_dump(tmp, ou->key, 20); printf("Username.......%s\n", ou->username); printf("Key............%s\n", tmp); printf("Count..........%" PRIu64 " (0x%" PRIx64 ")\n", ou->count, ou->count); printf("Count Ceiling..%" PRIu64 " (0x%" PRIx64 ")\n", ou->count_ceil, ou->count_ceil); printf("Version........%u\n", (u_int)ou->version); printf("Status.........%s (%u)\n", otp_l_status[ou->status], (u_int)ou->status); printf("Format.........%s (%u)\n", otp_l_format[ou->format], (u_int)ou->format); printf("Type...........%s (%u)\n", otp_l_type[ou->type], (u_int)ou->type); printf("Flags..........%2.2x", (u_int)ou->flags); if (ou->flags) printf(" ["); if (ou->flags & OTP_USER_FLAGS_DSPCNT) printf(" display-count"); if (ou->flags) printf(" ]"); printf("\n"); } /* otp_urec_disp */ /* * function: otp_urec_dispsc() * * format and display ou to stdout in SC friendly format * * arguments: * otpctx - otp db context returned by otp_db_open() * ou - otp_user record opened with otp_urec_open() * sc_index - SC index * sc_hostname - SC hostname * sc_flags - SC flag bits (OR'd to hostname in output) * * returns: <0 failure * 0 success * */ void otp_urec_dispsc(struct otp_ctx *otpctx, struct otp_user *ou, uint8_t sc_index, char *sc_hostname, uint8_t *sc_flags) { char fmt_buf[1024], tmp_sc_hostname[SC_HOSTNAME_LEN]; uint32_t tmpc32; int l; if (otp_db_valid(otpctx, "otp_urec_dispsc") < 0) return; l = strlen(sc_hostname); if (l > SC_HOSTNAME_LEN) l = SC_HOSTNAME_LEN; bzero(tmp_sc_hostname, SC_HOSTNAME_LEN); bcopy(sc_hostname, tmp_sc_hostname, l); /* set flag bits */ for (l = 0; l < SC_HOSTNAME_LEN; ++l) tmp_sc_hostname[l] |= sc_flags[l]; tmpc32 = ou->count; #if BYTE_ORDER == LITTLE_ENDIAN SWAP32(tmpc32) #endif /* BYTE_ORDER */ str_hex_dump(fmt_buf, (u_char*)&sc_index, SC_INDEX_LEN); printf("%s:", fmt_buf); str_hex_dump(fmt_buf, (u_char*)&tmpc32, SC_COUNT32_LEN); printf("%s:", fmt_buf); str_hex_dump(fmt_buf, (u_char*)tmp_sc_hostname, SC_HOSTNAME_LEN); printf("%s:", fmt_buf); str_hex_dump(fmt_buf, ou->key, SC_HOTPKEY_LEN); printf("%s\n", fmt_buf); } /* otp_urec_dispsc */ #ifdef OTPLIB_EXAMPLE #include #include "otplib.h" #include "ffdb.h" int main(int argc, char **argv) { struct otp_ctx *otpctx, *otpctx2; struct otp_user ou; uint8_t key160[OTP_HOTP_KEY_LEN]; uint8_t key40[OTP_HOTP_HEX40_LEN]; uint8_t crsp[20]; int i, ret; xerr_setid(argv[0]); otpctx = otp_db_open("/tmp/otpdb", OTP_DB_VERBOSE|OTP_DB_CREATE); ret = (otpctx == 0L); printf("otp_db_open(): %d\n", ret); otpctx2 = otp_db_open("/tmp/otpdb2", OTP_DB_VERBOSE|OTP_DB_CREATE); ret = (otpctx == 0L); printf("otp_db_open2(): %d\n", ret); for (i = 0; i < OTP_HOTP_KEY_LEN; ++i) key160[i] = i; for (i = 0; i < OTP_HOTP_HEX40_LEN; ++i) key40[i] = i; ret = otp_user_add(otpctx, "maf3", key160, OTP_HOTP_KEY_LEN, 0LL, 0xFFFFFFFFFFFFFFFFLL, OTP_STATUS_ACTIVE, OTP_TYPE_HOTP, OTP_FORMAT_HEX40, OTP_VERSION); printf("otp_user_add(): %d\n", ret); ret = otp_user_exists(otpctx, "maf"); printf("otp_user_exists(): %d\n", ret); ret = otp_urec_open(otpctx, "maf", &ou, O_RDWR, FFDB_OP_LOCK_EX); printf("otp_urec_open(): %d\n", ret); ret = otp_urec_get(otpctx, &ou); printf("otp_urec_get(): %d\n", ret); ret = otp_urec_put(otpctx, &ou); printf("otp_urec_put(): %d\n", ret); ret = otp_urec_crsp(otpctx, &ou, 0LL, crsp, 20); printf("otp_urec_crsp(): %d %s\n", ret, crsp); ret = otp_urec_close(otpctx, &ou); printf("otp_urec_close(): %d\n", ret); /* crsp[0] = 'F'; */ ret = otp_user_auth(otpctx, "maf", crsp, OTP_HOTP_WINDOW); printf("otp_user_auth(): %d\n", ret); /* ret = otp_user_rm(otpctx, "maf"); printf("otp_user_rm(): %d\n", ret); */ /* ret = otp_db_dump(otpctx); printf("otp_db_dump(): %d\n", ret); */ ret = otp_db_load(otpctx2, (char*)0L); printf("otp_db_load(): %d\n", ret); ret = otp_db_close(otpctx); printf("otp_db_close(): %d\n", ret); } /* main */ #endif /* OTPLIB_EXAMPLE */