From 28630d550ff1f756fadc00a81595cd69c8b11ab6 Mon Sep 17 00:00:00 2001 From: Nathaniel McCallum Date: Mon, 6 Jan 2014 16:50:07 -0500 Subject: Add libotp internal library for slapi plugins --- daemons/configure.ac | 1 + daemons/ipa-slapi-plugins/Makefile.am | 1 + daemons/ipa-slapi-plugins/libotp/Makefile.am | 9 + daemons/ipa-slapi-plugins/libotp/libotp.c | 510 +++++++++++++++++++++++++++ daemons/ipa-slapi-plugins/libotp/libotp.h | 95 +++++ daemons/ipa-slapi-plugins/libotp/librfc.c | 170 +++++++++ daemons/ipa-slapi-plugins/libotp/librfc.h | 63 ++++ daemons/ipa-slapi-plugins/libotp/t_librfc.c | 121 +++++++ 8 files changed, 970 insertions(+) create mode 100644 daemons/ipa-slapi-plugins/libotp/Makefile.am create mode 100644 daemons/ipa-slapi-plugins/libotp/libotp.c create mode 100644 daemons/ipa-slapi-plugins/libotp/libotp.h create mode 100644 daemons/ipa-slapi-plugins/libotp/librfc.c create mode 100644 daemons/ipa-slapi-plugins/libotp/librfc.h create mode 100644 daemons/ipa-slapi-plugins/libotp/t_librfc.c diff --git a/daemons/configure.ac b/daemons/configure.ac index 7086d8e10..b0bbe96a6 100644 --- a/daemons/configure.ac +++ b/daemons/configure.ac @@ -309,6 +309,7 @@ AC_CONFIG_FILES([ ipa-sam/Makefile ipa-otpd/Makefile ipa-slapi-plugins/Makefile + ipa-slapi-plugins/libotp/Makefile ipa-slapi-plugins/ipa-cldap/Makefile ipa-slapi-plugins/ipa-dns/Makefile ipa-slapi-plugins/ipa-enrollment/Makefile diff --git a/daemons/ipa-slapi-plugins/Makefile.am b/daemons/ipa-slapi-plugins/Makefile.am index 08c7558c8..40725d225 100644 --- a/daemons/ipa-slapi-plugins/Makefile.am +++ b/daemons/ipa-slapi-plugins/Makefile.am @@ -1,6 +1,7 @@ NULL = SUBDIRS = \ + libotp \ ipa-cldap \ ipa-dns \ ipa-enrollment \ diff --git a/daemons/ipa-slapi-plugins/libotp/Makefile.am b/daemons/ipa-slapi-plugins/libotp/Makefile.am new file mode 100644 index 000000000..6aa60c56a --- /dev/null +++ b/daemons/ipa-slapi-plugins/libotp/Makefile.am @@ -0,0 +1,9 @@ +MAINTAINERCLEANFILES = *~ Makefile.in +AM_CPPFLAGS = -I/usr/include/dirsrv + +noinst_LTLIBRARIES = librfc.la libotp.la +libotp_la_LIBADD = librfc.la + +check_PROGRAMS = t_librfc +TESTS = $(check_PROGRAMS) +t_librfc_LDADD = $(NSPR_LIBS) $(NSS_LIBS) librfc.la diff --git a/daemons/ipa-slapi-plugins/libotp/libotp.c b/daemons/ipa-slapi-plugins/libotp/libotp.c new file mode 100644 index 000000000..3fb298035 --- /dev/null +++ b/daemons/ipa-slapi-plugins/libotp/libotp.c @@ -0,0 +1,510 @@ +/** BEGIN COPYRIGHT BLOCK + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Additional permission under GPLv3 section 7: + * + * In the following paragraph, "GPL" means the GNU General Public + * License, version 3 or any later version, and "Non-GPL Code" means + * code that is governed neither by the GPL nor a license + * compatible with the GPL. + * + * You may link the code of this Program with Non-GPL Code and convey + * linked combinations including the two, provided that such Non-GPL + * Code only links to the code of this Program through those well + * defined interfaces identified in the file named EXCEPTION found in + * the source code files (the "Approved Interfaces"). The files of + * Non-GPL Code may instantiate templates or use macros or inline + * functions from the Approved Interfaces without causing the resulting + * work to be covered by the GPL. Only the copyright holders of this + * Program may make changes or additions to the list of Approved + * Interfaces. + * + * Authors: + * Nathaniel McCallum + * + * Copyright (C) 2013 Red Hat, Inc. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +#include "libotp.h" +#include "librfc.h" + +#include +#include + +#define TOKEN(s) "ipaToken" s +#define O(s) TOKEN("OTP" s) +#define T(s) TOKEN("TOTP" s) + +#define IPA_OTP_DEFAULT_TOKEN_STEP 30 +#define IPA_OTP_OBJCLS_FILTER "(objectClass=ipaTokenTOTP)" + + +enum otptoken_type { + OTPTOKEN_NONE = 0, + OTPTOKEN_TOTP, +}; + +struct otptoken { + Slapi_ComponentId *plugin_id; + Slapi_DN *sdn; + struct hotp_token token; + enum otptoken_type type; + struct { + unsigned int step; + int offset; + } totp; +}; + +static const char *get_basedn(Slapi_DN *dn) +{ + Slapi_DN *suffix = NULL; + void *node = NULL; + + for (suffix = slapi_get_first_suffix(&node, 0); + suffix != NULL; + suffix = slapi_get_next_suffix(&node, 0)) { + if (slapi_sdn_issuffix(dn, suffix)) + return (char *) slapi_sdn_get_dn(suffix); + } + + return NULL; +} + +static inline bool is_algo_valid(const char *algo) +{ + static const char *valid_algos[] = { "sha1", "sha256", "sha384", + "sha512", NULL }; + int i, ret; + + for (i = 0; valid_algos[i]; i++) { + ret = strcasecmp(algo, valid_algos[i]); + if (ret == 0) + return true; + } + + return false; +} + +static const struct berval *entry_attr_get_berval(const Slapi_Entry* e, + const char *type) +{ + Slapi_Attr* attr = NULL; + Slapi_Value *v; + int ret; + + ret = slapi_entry_attr_find(e, type, &attr); + if (ret != 0 || attr == NULL) + return NULL; + + ret = slapi_attr_first_value(attr, &v); + if (ret < 0) + return NULL; + + return slapi_value_get_berval(v); +} + +static bool validate(struct otptoken *token, time_t now, ssize_t step, + uint32_t first, const uint32_t *second) +{ + uint32_t tmp; + + switch (token->type) { + case OTPTOKEN_TOTP: + step = (now + token->totp.offset) / token->totp.step + step; + break; + default: + return false; + } + + if (!hotp(&token->token, step, &tmp)) + return false; + + if (first != tmp) + return false; + + if (second == NULL) + return true; + + if (!hotp(&token->token, step + 1, &tmp)) + return false; + + return *second == tmp; +} + +static bool writeback(struct otptoken *token, ssize_t step, bool sync) +{ + Slapi_Value *svals[] = { NULL, NULL }; + Slapi_PBlock *pb = NULL; + Slapi_Mods *mods = NULL; + bool success = false; + const char *attr; + int value; + int ret; + + switch (token->type) { + case OTPTOKEN_TOTP: + if (!sync) + return true; + attr = T("clockOffset"); + value = token->totp.offset + step * token->totp.step; + break; + default: + return false; + } + + /* Create the value. */ + svals[0] = slapi_value_new(); + if (slapi_value_set_int(svals[0], value) != 0) + goto error; + + /* Create the mods. */ + mods = slapi_mods_new(); + slapi_mods_add_mod_values(mods, LDAP_MOD_REPLACE, attr, svals); + + /* Perform the modification. */ + pb = slapi_pblock_new(); + slapi_modify_internal_set_pb(pb, slapi_sdn_get_dn(token->sdn), + slapi_mods_get_ldapmods_byref(mods), + NULL, NULL, token->plugin_id, 0); + if (slapi_modify_internal_pb(pb) != 0) + goto error; + if (slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &ret) != 0) + goto error; + if (ret != LDAP_SUCCESS) + goto error; + + /* Save our modifications to the object. */ + switch (token->type) { + case OTPTOKEN_TOTP: + token->totp.offset = value; + break; + default: + break; + } + + success = true; + +error: + slapi_pblock_destroy(pb); + slapi_mods_free(&mods); + return success; +} + +static void otptoken_free(struct otptoken *token) +{ + if (token == NULL) + return; + + slapi_sdn_free(&token->sdn); + free(token->token.key.bytes); + slapi_ch_free_string(&token->token.algo); + free(token); +} + +void otptoken_free_array(struct otptoken **tokens) +{ + if (tokens == NULL) + return; + + for (size_t i = 0; tokens[i] != NULL; i++) + otptoken_free(tokens[i]); + + free(tokens); +} + +static struct otptoken *otptoken_new(Slapi_ComponentId *id, Slapi_Entry *entry) +{ + const struct berval *tmp; + struct otptoken *token; + char **vals; + + token = calloc(1, sizeof(struct otptoken)); + if (token == NULL) + return NULL; + token->plugin_id = id; + + /* Get the token type. */ + vals = slapi_entry_attr_get_charray(entry, "objectClass"); + if (vals == NULL) + goto error; + token->type = OTPTOKEN_NONE; + for (int i = 0; vals[i] != NULL; i++) { + if (strcasecmp(vals[i], "ipaTokenTOTP") == 0) + token->type = OTPTOKEN_TOTP; + } + slapi_ch_array_free(vals); + if (token->type == OTPTOKEN_NONE) + goto error; + + /* Get SDN. */ + token->sdn = slapi_sdn_dup(slapi_entry_get_sdn(entry)); + if (token->sdn == NULL) + goto error; + + /* Get key. */ + tmp = entry_attr_get_berval(entry, O("key")); + if (tmp == NULL) + goto error; + token->token.key.len = tmp->bv_len; + token->token.key.bytes = malloc(token->token.key.len); + if (token->token.key.bytes == NULL) + goto error; + memcpy(token->token.key.bytes, tmp->bv_val, token->token.key.len); + + /* Get length. */ + token->token.digits = slapi_entry_attr_get_int(entry, O("digits")); + if (token->token.digits != 6 && token->token.digits != 8) + goto error; + + /* Get algorithm. */ + token->token.algo = slapi_entry_attr_get_charptr(entry, O("algorithm")); + if (token->token.algo == NULL) + token->token.algo = slapi_ch_strdup("sha1"); + if (!is_algo_valid(token->token.algo)) + goto error; + + switch (token->type) { + case OTPTOKEN_TOTP: + /* Get offset. */ + token->totp.offset = slapi_entry_attr_get_int(entry, T("clockOffset")); + + /* Get step. */ + token->totp.step = slapi_entry_attr_get_uint(entry, T("timeStep")); + if (token->totp.step == 0) + token->totp.step = IPA_OTP_DEFAULT_TOKEN_STEP; + break; + default: + break; + } + + return token; + +error: + otptoken_free(token); + return NULL; +} + +static struct otptoken **find(Slapi_ComponentId *id, const char *user_dn, + const char *token_dn, const char *intfilter, + const char *extfilter) +{ + struct otptoken **tokens = NULL; + Slapi_Entry **entries = NULL; + Slapi_PBlock *pb = NULL; + Slapi_DN *sdn = NULL; + char *filter = NULL; + size_t count = 0; + int result = -1; + + if (intfilter == NULL) + intfilter = ""; + + if (extfilter == NULL) + extfilter = ""; + + /* Create the filter. */ + if (user_dn == NULL) { + filter = "(&" IPA_OTP_OBJCLS_FILTER "%s%s)"; + filter = slapi_filter_sprintf(filter, intfilter, extfilter); + } else { + filter = "(&" IPA_OTP_OBJCLS_FILTER "(ipatokenOwner=%s%s)%s%s)"; + filter = slapi_filter_sprintf(filter, ESC_AND_NORM_NEXT_VAL, + user_dn, intfilter, extfilter); + } + + /* Create the search. */ + pb = slapi_pblock_new(); + if (token_dn != NULL) { + /* Find only the token specified. */ + slapi_search_internal_set_pb(pb, token_dn, LDAP_SCOPE_BASE, filter, + NULL, 0, NULL, NULL, id, 0); + } else { + sdn = slapi_sdn_new_dn_byval(user_dn); + if (sdn == NULL) + goto error; + + /* Find all user tokens. */ + slapi_search_internal_set_pb(pb, get_basedn(sdn), + LDAP_SCOPE_SUBTREE, filter, NULL, + 0, NULL, NULL, id, 0); + } + slapi_search_internal_pb(pb); + slapi_ch_free_string(&filter); + + /* Get the results. */ + slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &result); + if (result != LDAP_SUCCESS) + goto error; + slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &entries); + if (entries == NULL) + goto error; + + /* TODO: Can I get the count another way? */ + for (count = 0; entries[count] != NULL; count++) + continue; + + /* Create the array. */ + tokens = calloc(count + 1, sizeof(*tokens)); + if (tokens == NULL) + goto error; + for (count = 0; entries[count] != NULL; count++) { + tokens[count] = otptoken_new(id, entries[count]); + if (tokens[count] == NULL) { + otptoken_free_array(tokens); + tokens = NULL; + goto error; + } + } + +error: + if (sdn != NULL) + slapi_sdn_free(&sdn); + slapi_pblock_destroy(pb); + return tokens; +} + +struct otptoken **otptoken_find(Slapi_ComponentId *id, const char *user_dn, + const char *token_dn, bool active, + const char *filter) +{ + static const char template[] = + "(|(ipatokenNotBefore<=%04d%02d%02d%02d%02d%02dZ)(!(ipatokenNotBefore=*)))" + "(|(ipatokenNotAfter>=%04d%02d%02d%02d%02d%02dZ)(!(ipatokenNotAfter=*)))" + "(|(ipatokenDisabled=FALSE)(!(ipatokenDisabled=*)))"; + char actfilt[sizeof(template)]; + struct tm tm; + time_t now; + + if (!active) + return find(id, user_dn, token_dn, NULL, filter); + + /* Get the current time. */ + if (time(&now) == (time_t) -1) + return NULL; + if (gmtime_r(&now, &tm) == NULL) + return NULL; + + /* Get the current time string. */ + if (snprintf(actfilt, sizeof(actfilt), template, + tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, + tm.tm_hour, tm.tm_min, tm.tm_sec, + tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, + tm.tm_hour, tm.tm_min, tm.tm_sec) < 0) + return NULL; + + return find(id, user_dn, token_dn, actfilt, filter); +} + +int otptoken_get_digits(struct otptoken *token) +{ + return token == NULL ? 0 : token->token.digits; +} + +const Slapi_DN *otptoken_get_sdn(struct otptoken *token) +{ + return token->sdn; +} + +bool otptoken_validate(struct otptoken *token, size_t steps, uint32_t code) +{ + time_t now = 0; + + if (token == NULL) + return false; + + /* We only need the local time for time-based tokens. */ + if (token->type == OTPTOKEN_TOTP && time(&now) == (time_t) -1) + return false; + + for (int i = 0; i <= steps; i++) { + /* Validate the positive step. */ + if (validate(token, now, i, code, NULL)) + return writeback(token, i + 1, false); + + if (i == 0) + continue; + + /* Validate the negative step. */ + if (validate(token, now, 0 - i, code, NULL)) + return writeback(token, 0 - i + 1, false); + } + + return false; +} + +bool otptoken_validate_string(struct otptoken *token, size_t steps, + const char *code, ssize_t len, bool tail) +{ + uint32_t otp; + + if (token == NULL || code == NULL) + return false; + + if (len < 0) + len = strlen(code); + + if (len < token->token.digits) + return false; + + if (tail) + code = &code[len - token->token.digits]; + len = token->token.digits; + + /* + * Convert code string to decimal. + * + * NOTE: We can't use atol() or strtoul() because: + * 1. We may have leading zeros (atol() fails here). + * 2. Neither support limiting conversion by length. + */ + otp = 0; + for (ssize_t i = 0; i < len; i++) { + if (code[i] < '0' || code[i] > '9') + return false; + otp *= 10; + otp += code[i] - '0'; + } + + return otptoken_validate(token, steps, otp); +} + +bool otptoken_sync(struct otptoken * const *tokens, size_t steps, + uint32_t first_code, uint32_t second_code) +{ + time_t now = 0; + + if (tokens == NULL) + return false; + + if (time(&now) == (time_t) -1) + return false; + + for (int i = 0; i <= steps; i++) { + for (int j = 0; tokens[j] != NULL; j++) { + /* Validate the positive step. */ + if (validate(tokens[j], now, i, first_code, &second_code)) + return writeback(tokens[j], i + 2, true); + + if (i == 0) + continue; + + /* Validate the negative step. */ + if (validate(tokens[j], now, 0 - i, first_code, &second_code)) + return writeback(tokens[j], 0 - i + 2, true); + } + } + + return false; +} diff --git a/daemons/ipa-slapi-plugins/libotp/libotp.h b/daemons/ipa-slapi-plugins/libotp/libotp.h new file mode 100644 index 000000000..7ed729337 --- /dev/null +++ b/daemons/ipa-slapi-plugins/libotp/libotp.h @@ -0,0 +1,95 @@ +/** BEGIN COPYRIGHT BLOCK + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Additional permission under GPLv3 section 7: + * + * In the following paragraph, "GPL" means the GNU General Public + * License, version 3 or any later version, and "Non-GPL Code" means + * code that is governed neither by the GPL nor a license + * compatible with the GPL. + * + * You may link the code of this Program with Non-GPL Code and convey + * linked combinations including the two, provided that such Non-GPL + * Code only links to the code of this Program through those well + * defined interfaces identified in the file named EXCEPTION found in + * the source code files (the "Approved Interfaces"). The files of + * Non-GPL Code may instantiate templates or use macros or inline + * functions from the Approved Interfaces without causing the resulting + * work to be covered by the GPL. Only the copyright holders of this + * Program may make changes or additions to the list of Approved + * Interfaces. + * + * Authors: + * Nathaniel McCallum + * + * Copyright (C) 2013 Red Hat, Inc. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +#ifndef LIBOTP_H_ +#define LIBOTP_H_ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include +#include +#include + +struct otptoken; + +/* Frees the token array. */ +void otptoken_free_array(struct otptoken **tokens); + +/* Find tokens. + * + * All criteria below are cumulative. For example, if you specify both dn and + * active and the token at the dn specified isn't active, an empty array will + * be returned. + * + * If user_dn is not NULL, the user's tokens are returned. + * + * If token_dn is not NULL, only this specified token is returned. + * + * If active is true, only tokens that are active are returned. + * + * If filter is not NULL, the filter will be added to the search criteria. + * + * Returns NULL on error. If no tokens are found, an empty array is returned. + * The array is NULL terminated. + */ +struct otptoken **otptoken_find(Slapi_ComponentId *id, const char *user_dn, + const char *token_dn, bool active, + const char *filter); + +/* Get the length of the token code. */ +int otptoken_get_digits(struct otptoken *token); + +/* Get the SDN of the token. */ +const Slapi_DN *otptoken_get_sdn(struct otptoken *token); + +/* Validate the token code within a range of steps. */ +bool otptoken_validate(struct otptoken *token, size_t steps, uint32_t code); + +/* Validate the token code within a range of steps. If tail is true, + * it will be assumed that the token is specified at the end of the string. */ +bool otptoken_validate_string(struct otptoken *token, size_t steps, + const char *code, ssize_t len, bool tail); + +/* Synchronize the token within a range of steps. */ +bool otptoken_sync(struct otptoken * const *tokens, size_t steps, + uint32_t first_code, uint32_t second_code); + +#endif /* LIBOTP_H_ */ diff --git a/daemons/ipa-slapi-plugins/libotp/librfc.c b/daemons/ipa-slapi-plugins/libotp/librfc.c new file mode 100644 index 000000000..d74820e95 --- /dev/null +++ b/daemons/ipa-slapi-plugins/libotp/librfc.c @@ -0,0 +1,170 @@ +/** BEGIN COPYRIGHT BLOCK + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Additional permission under GPLv3 section 7: + * + * In the following paragraph, "GPL" means the GNU General Public + * License, version 3 or any later version, and "Non-GPL Code" means + * code that is governed neither by the GPL nor a license + * compatible with the GPL. + * + * You may link the code of this Program with Non-GPL Code and convey + * linked combinations including the two, provided that such Non-GPL + * Code only links to the code of this Program through those well + * defined interfaces identified in the file named EXCEPTION found in + * the source code files (the "Approved Interfaces"). The files of + * Non-GPL Code may instantiate templates or use macros or inline + * functions from the Approved Interfaces without causing the resulting + * work to be covered by the GPL. Only the copyright holders of this + * Program may make changes or additions to the list of Approved + * Interfaces. + * + * Authors: + * Nathaniel McCallum + * + * Copyright (C) 2013 Red Hat, Inc. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +/* + * This file contains an implementation of HOTP (RFC 4226) and TOTP (RFC 6238). + * For details of how these algorithms work, please see the relevant RFCs. + */ + +#include "librfc.h" +#include + +#include +#include +#include +#include + +struct digest_buffer { + uint8_t buf[SHA512_LENGTH]; + unsigned int len; +}; + +static const struct { + const char *algo; + CK_MECHANISM_TYPE mech; +} algo2mech[] = { + { "sha1", CKM_SHA_1_HMAC }, + { "sha256", CKM_SHA256_HMAC }, + { "sha384", CKM_SHA384_HMAC }, + { "sha512", CKM_SHA512_HMAC }, + { } +}; + +/* + * This code is mostly cargo-cult taken from here: + * http://www.mozilla.org/projects/security/pki/nss/tech-notes/tn5.html + * + * It should implement HMAC with the given mechanism (SHA: 1, 256, 384, 512). + */ +static bool hmac(SECItem *key, CK_MECHANISM_TYPE mech, const SECItem *in, + struct digest_buffer *out) +{ + SECItem param = { siBuffer, NULL, 0 }; + PK11SlotInfo *slot = NULL; + PK11SymKey *symkey = NULL; + PK11Context *ctx = NULL; + bool ret = false; + SECStatus s; + + slot = PK11_GetBestSlot(mech, NULL); + if (slot == NULL) { + slot = PK11_GetInternalKeySlot(); + if (slot == NULL) { + goto done; + } + } + + symkey = PK11_ImportSymKey(slot, mech, PK11_OriginUnwrap, + CKA_SIGN, key, NULL); + if (symkey == NULL) + goto done; + + ctx = PK11_CreateContextBySymKey(mech, CKA_SIGN, symkey, ¶m); + if (ctx == NULL) + goto done; + + s = PK11_DigestBegin(ctx); + if (s != SECSuccess) + goto done; + + s = PK11_DigestOp(ctx, in->data, in->len); + if (s != SECSuccess) + goto done; + + s = PK11_DigestFinal(ctx, out->buf, &out->len, sizeof(out->buf)); + if (s != SECSuccess) + goto done; + + ret = true; + +done: + if (ctx != NULL) + PK11_DestroyContext(ctx, PR_TRUE); + if (symkey != NULL) + PK11_FreeSymKey(symkey); + if (slot != NULL) + PK11_FreeSlot(slot); + return ret; +} + +/* + * An implementation of HOTP (RFC 4226). + */ +bool hotp(const struct hotp_token *token, uint64_t counter, uint32_t *out) +{ + const SECItem cntr = { siBuffer, (uint8_t *) &counter, sizeof(counter) }; + SECItem keyitm = { siBuffer, token->key.bytes, token->key.len }; + CK_MECHANISM_TYPE mech = CKM_SHA_1_HMAC; + PRUint64 offset, binary, div; + struct digest_buffer digest; + int digits = token->digits; + int i; + + /* Convert counter to network byte order. */ + counter = PR_htonll(counter); + + /* Find the mech. */ + for (i = 0; algo2mech[i].algo; i++) { + if (strcasecmp(algo2mech[i].algo, token->algo) == 0) { + mech = algo2mech[i].mech; + break; + } + } + + /* Create the digits divisor. */ + for (div = 1; digits > 0; digits--) { + div *= 10; + } + + /* Do the digest. */ + if (!hmac(&keyitm, mech, &cntr, &digest)) { + return false; + } + + /* Truncate. */ + offset = digest.buf[digest.len - 1] & 0xf; + binary = (digest.buf[offset + 0] & 0x7f) << 0x18; + binary |= (digest.buf[offset + 1] & 0xff) << 0x10; + binary |= (digest.buf[offset + 2] & 0xff) << 0x08; + binary |= (digest.buf[offset + 3] & 0xff) << 0x00; + binary = binary % div; + + *out = binary; + return true; +} diff --git a/daemons/ipa-slapi-plugins/libotp/librfc.h b/daemons/ipa-slapi-plugins/libotp/librfc.h new file mode 100644 index 000000000..04b117600 --- /dev/null +++ b/daemons/ipa-slapi-plugins/libotp/librfc.h @@ -0,0 +1,63 @@ +/** BEGIN COPYRIGHT BLOCK + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Additional permission under GPLv3 section 7: + * + * In the following paragraph, "GPL" means the GNU General Public + * License, version 3 or any later version, and "Non-GPL Code" means + * code that is governed neither by the GPL nor a license + * compatible with the GPL. + * + * You may link the code of this Program with Non-GPL Code and convey + * linked combinations including the two, provided that such Non-GPL + * Code only links to the code of this Program through those well + * defined interfaces identified in the file named EXCEPTION found in + * the source code files (the "Approved Interfaces"). The files of + * Non-GPL Code may instantiate templates or use macros or inline + * functions from the Approved Interfaces without causing the resulting + * work to be covered by the GPL. Only the copyright holders of this + * Program may make changes or additions to the list of Approved + * Interfaces. + * + * Authors: + * Nathaniel McCallum + * + * Copyright (C) 2013 Red Hat, Inc. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +#ifndef LIBRFC_H_ +#define LIBRFC_H_ + +#include +#include +#include + +struct hotp_token_key { + uint8_t *bytes; + size_t len; +}; + +struct hotp_token { + struct hotp_token_key key; + char *algo; + int digits; +}; + +/* + * An implementation of HOTP (RFC 4226). + */ +bool hotp(const struct hotp_token *token, uint64_t counter, uint32_t *out); + +#endif /* LIBRFC_H_ */ diff --git a/daemons/ipa-slapi-plugins/libotp/t_librfc.c b/daemons/ipa-slapi-plugins/libotp/t_librfc.c new file mode 100644 index 000000000..f7eab7f78 --- /dev/null +++ b/daemons/ipa-slapi-plugins/libotp/t_librfc.c @@ -0,0 +1,121 @@ +/** BEGIN COPYRIGHT BLOCK + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Additional permission under GPLv3 section 7: + * + * In the following paragraph, "GPL" means the GNU General Public + * License, version 3 or any later version, and "Non-GPL Code" means + * code that is governed neither by the GPL nor a license + * compatible with the GPL. + * + * You may link the code of this Program with Non-GPL Code and convey + * linked combinations including the two, provided that such Non-GPL + * Code only links to the code of this Program through those well + * defined interfaces identified in the file named EXCEPTION found in + * the source code files (the "Approved Interfaces"). The files of + * Non-GPL Code may instantiate templates or use macros or inline + * functions from the Approved Interfaces without causing the resulting + * work to be covered by the GPL. Only the copyright holders of this + * Program may make changes or additions to the list of Approved + * Interfaces. + * + * Authors: + * Nathaniel McCallum + * + * Copyright (C) 2013 Red Hat, Inc. + * All rights reserved. + * END COPYRIGHT BLOCK **/ + +#include "librfc.h" + +#include +#include +#include +#include +#include + +#define KEY(s) { (uint8_t *) s, sizeof(s) - 1 } + +/* All HOTP test examples from RFC 4226 (Appendix D). */ +static const struct hotp_token hotp_token = { + KEY("12345678901234567890"), + "sha1", + 6 +}; +static const uint32_t hotp_answers[] = { + 755224, + 287082, + 359152, + 969429, + 338314, + 254676, + 287922, + 162583, + 399871, + 520489 +}; + +/* All TOTP test examples from RFC 6238 (Appendix B). */ +#define SHA1 { KEY("12345678901234567890"), "sha1", 8 } +#define SHA256 { KEY("12345678901234567890123456789012"), "sha256", 8 } +#define SHA512 { KEY("12345678901234567890123456789012" \ + "34567890123456789012345678901234"), "sha512", 8 } +static const struct { + struct hotp_token token; + time_t time; + uint32_t answer; +} totp_tests[] = { + { SHA1, 59, 94287082 }, + { SHA256, 59, 46119246 }, + { SHA512, 59, 90693936 }, + { SHA1, 1111111109, 7081804 }, + { SHA256, 1111111109, 68084774 }, + { SHA512, 1111111109, 25091201 }, + { SHA1, 1111111111, 14050471 }, + { SHA256, 1111111111, 67062674 }, + { SHA512, 1111111111, 99943326 }, + { SHA1, 1234567890, 89005924 }, + { SHA256, 1234567890, 91819424 }, + { SHA512, 1234567890, 93441116 }, + { SHA1, 2000000000, 69279037 }, + { SHA256, 2000000000, 90698825 }, + { SHA512, 2000000000, 38618901 }, +#ifdef _LP64 /* Only do these tests on 64-bit systems. */ + { SHA1, 20000000000, 65353130 }, + { SHA256, 20000000000, 77737706 }, + { SHA512, 20000000000, 47863826 }, +#endif +}; + +int +main(int argc, const char *argv[]) +{ + uint32_t otp; + int i; + + NSS_NoDB_Init("."); + + for (i = 0; i < sizeof(hotp_answers) / sizeof(*hotp_answers); i++) { + assert(hotp(&hotp_token, i, &otp)); + assert(otp == hotp_answers[i]); + } + + for (i = 0; i < sizeof(totp_tests) / sizeof(*totp_tests); i++) { + assert(hotp(&totp_tests[i].token, totp_tests[i].time / 30, &otp)); + assert(otp == totp_tests[i].answer); + } + + NSS_Shutdown(); + return 0; +} -- cgit