Attached.
>From 035fe16ec8088effcd06824ae1aeee99222076b1 Mon Sep 17 00:00:00 2001
From: Nathaniel McCallum <npmccal...@redhat.com>
Date: Mon, 6 Jan 2014 16:50:07 -0500
Subject: [PATCH] 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    | 502 +++++++++++++++++++++++++++
 daemons/ipa-slapi-plugins/libotp/libotp.h    |  92 +++++
 daemons/ipa-slapi-plugins/libotp/librfc.c    | 168 +++++++++
 daemons/ipa-slapi-plugins/libotp/librfc.h    |  61 ++++
 daemons/ipa-slapi-plugins/libotp/t_librfc.c  | 119 +++++++
 8 files changed, 953 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 7086d8e1054ad9941232c75fc0a82e050a682247..b0bbe96a632337b973316a2849c9d35b5d642f5f 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 08c7558c812effc00ae940661e448779077fb409..40725d2259d09010d2f82381543fc77d84435040 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 0000000000000000000000000000000000000000..6aa60c56a2293916ba7c1d773968055c3f94a4fe
--- /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 0000000000000000000000000000000000000000..dd5ebe6a110c3ea7f61dde40dd71201deef4dd2e
--- /dev/null
+++ b/daemons/ipa-slapi-plugins/libotp/libotp.c
@@ -0,0 +1,502 @@
+/** 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; version 2 of the License.
+ *
+ * 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, write to the Free Software Foundation, Inc., 59 Temple
+ * Place, Suite 330, Boston, MA 02111-1307 USA.
+ *
+ * In addition, as a special exception, Red Hat, Inc. gives You the additional
+ * right to link the code of this Program with code not covered under the GNU
+ * General Public License ("Non-GPL Code") and to distribute linked combinations
+ * including the two, subject to the limitations in this paragraph. Non-GPL Code
+ * permitted under this exception must only link 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 GNU General Public License. Only Red Hat, Inc. may make changes or
+ * additions to the list of Approved Interfaces. You must obey the GNU General
+ * Public License in all respects for all of the Program code and other code used
+ * in conjunction with the Program except the Non-GPL Code covered by this
+ * exception. If you modify this file, you may extend this exception to your
+ * version of the file, but you are not obligated to do so. If you do not wish to
+ * provide this exception without modification, you must delete this exception
+ * statement from your version and license this file solely under the GPL without
+ * exception.
+ *
+ *
+ * Copyright (C) 2013 Red Hat, Inc.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+
+#include "libotp.h"
+#include "librfc.h"
+
+#include <time.h>
+#include <errno.h>
+
+#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, Slapi_DN *owner,
+                              Slapi_DN *dn, const char *intfilter,
+                              const char *extfilter)
+{
+    struct otptoken **tokens = NULL;
+    Slapi_Entry **entries = NULL;
+    Slapi_PBlock *pb = NULL;
+    char *filter = NULL;
+    size_t count = 0;
+    int result = -1;
+
+    if (intfilter == NULL)
+        intfilter = "";
+
+    if (extfilter == NULL)
+        extfilter = "";
+
+    /* Create the filter. */
+    if (owner == 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,
+                                      slapi_sdn_get_dn(owner),
+                                      intfilter, extfilter);
+    }
+
+    /* Create the search. */
+    pb = slapi_pblock_new();
+    if (dn != NULL) {
+        /* Find only the token specified. */
+        slapi_search_internal_set_pb(pb, slapi_sdn_get_dn(dn),
+                                     LDAP_SCOPE_BASE, filter, NULL, 0,
+                                     NULL, NULL, id, 0);
+    } else {
+        /* Find all user tokens. */
+        slapi_search_internal_set_pb(pb, get_basedn(owner),
+                                     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:
+    slapi_pblock_destroy(pb);
+    return tokens;
+}
+
+struct otptoken **otptoken_find(Slapi_ComponentId *id, Slapi_DN *owner,
+                                Slapi_DN *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, owner, 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, owner, 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 0000000000000000000000000000000000000000..d8afd6dd8e829b71638a51ac409be0f75430c113
--- /dev/null
+++ b/daemons/ipa-slapi-plugins/libotp/libotp.h
@@ -0,0 +1,92 @@
+/** 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; version 2 of the License.
+ *
+ * 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, write to the Free Software Foundation, Inc., 59 Temple
+ * Place, Suite 330, Boston, MA 02111-1307 USA.
+ *
+ * In addition, as a special exception, Red Hat, Inc. gives You the additional
+ * right to link the code of this Program with code not covered under the GNU
+ * General Public License ("Non-GPL Code") and to distribute linked combinations
+ * including the two, subject to the limitations in this paragraph. Non-GPL Code
+ * permitted under this exception must only link 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 GNU General Public License. Only Red Hat, Inc. may make changes or
+ * additions to the list of Approved Interfaces. You must obey the GNU General
+ * Public License in all respects for all of the Program code and other code used
+ * in conjunction with the Program except the Non-GPL Code covered by this
+ * exception. If you modify this file, you may extend this exception to your
+ * version of the file, but you are not obligated to do so. If you do not wish to
+ * provide this exception without modification, you must delete this exception
+ * statement from your version and license this file solely under the GPL without
+ * exception.
+ *
+ *
+ * Copyright (C) 2013 Red Hat, Inc.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+
+#ifndef LIBOTP_H_
+#define LIBOTP_H_
+
+#ifdef HAVE_CONFIG_H
+#  include <config.h>
+#endif
+
+#include <dirsrv/slapi-plugin.h>
+#include <stdbool.h>
+#include <stdlib.h>
+
+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 owner is not NULL, the owner's tokens are returned.
+ *
+ * If 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, Slapi_DN *owner,
+                                Slapi_DN *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 0000000000000000000000000000000000000000..4ec7703f0e35c7b27f9b926a3c1987c352a585dd
--- /dev/null
+++ b/daemons/ipa-slapi-plugins/libotp/librfc.c
@@ -0,0 +1,168 @@
+/** 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; version 3 of the License.
+ *
+ * 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, write to the Free Software Foundation, Inc., 59 Temple
+ * Place, Suite 330, Boston, MA 02111-1307 USA.
+ *
+ * In addition, as a special exception, Red Hat, Inc. gives You the additional
+ * right to link the code of this Program with code not covered under the GNU
+ * General Public License ("Non-GPL Code") and to distribute linked combinations
+ * including the two, subject to the limitations in this paragraph. Non-GPL Code
+ * permitted under this exception must only link 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 GNU General Public License. Only Red Hat, Inc. may make changes or
+ * additions to the list of Approved Interfaces. You must obey the GNU General
+ * Public License in all respects for all of the Program code and other code used
+ * in conjunction with the Program except the Non-GPL Code covered by this
+ * exception. If you modify this file, you may extend this exception to your
+ * version of the file, but you are not obligated to do so. If you do not wish to
+ * provide this exception without modification, you must delete this exception
+ * statement from your version and license this file solely under the GPL without
+ * exception.
+ *
+ *
+ * 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 <time.h>
+
+#include <nss.h>
+#include <pk11pub.h>
+#include <hasht.h>
+#include <prnetdb.h>
+
+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, &param);
+    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 0000000000000000000000000000000000000000..2d5c2b7fbf189483be9ffe45ed9ace4f0d282490
--- /dev/null
+++ b/daemons/ipa-slapi-plugins/libotp/librfc.h
@@ -0,0 +1,61 @@
+/** 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; version 2 of the License.
+ *
+ * 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, write to the Free Software Foundation, Inc., 59 Temple
+ * Place, Suite 330, Boston, MA 02111-1307 USA.
+ *
+ * In addition, as a special exception, Red Hat, Inc. gives You the additional
+ * right to link the code of this Program with code not covered under the GNU
+ * General Public License ("Non-GPL Code") and to distribute linked combinations
+ * including the two, subject to the limitations in this paragraph. Non-GPL Code
+ * permitted under this exception must only link 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 GNU General Public License. Only Red Hat, Inc. may make changes or
+ * additions to the list of Approved Interfaces. You must obey the GNU General
+ * Public License in all respects for all of the Program code and other code used
+ * in conjunction with the Program except the Non-GPL Code covered by this
+ * exception. If you modify this file, you may extend this exception to your
+ * version of the file, but you are not obligated to do so. If you do not wish to
+ * provide this exception without modification, you must delete this exception
+ * statement from your version and license this file solely under the GPL without
+ * exception.
+ *
+ *
+ * Copyright (C) 2013 Red Hat, Inc.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+
+#ifndef LIBRFC_H_
+#define LIBRFC_H_
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+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 0000000000000000000000000000000000000000..e5665e9f9583bacd8dd2d9043160eecdde694b79
--- /dev/null
+++ b/daemons/ipa-slapi-plugins/libotp/t_librfc.c
@@ -0,0 +1,119 @@
+/** 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; version 3 of the License.
+ *
+ * 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, write to the Free Software Foundation, Inc., 59 Temple
+ * Place, Suite 330, Boston, MA 02111-1307 USA.
+ *
+ * In addition, as a special exception, Red Hat, Inc. gives You the additional
+ * right to link the code of this Program with code not covered under the GNU
+ * General Public License ("Non-GPL Code") and to distribute linked combinations
+ * including the two, subject to the limitations in this paragraph. Non-GPL Code
+ * permitted under this exception must only link 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 GNU General Public License. Only Red Hat, Inc. may make changes or
+ * additions to the list of Approved Interfaces. You must obey the GNU General
+ * Public License in all respects for all of the Program code and other code used
+ * in conjunction with the Program except the Non-GPL Code covered by this
+ * exception. If you modify this file, you may extend this exception to your
+ * version of the file, but you are not obligated to do so. If you do not wish to
+ * provide this exception without modification, you must delete this exception
+ * statement from your version and license this file solely under the GPL without
+ * exception.
+ *
+ *
+ * Copyright (C) 2013 Red Hat, Inc.
+ * All rights reserved.
+ * END COPYRIGHT BLOCK **/
+
+#include "librfc.h"
+
+#include <assert.h>
+#include <stddef.h>
+#include <time.h>
+#include <string.h>
+#include <nss.h>
+
+#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;
+}
-- 
1.8.4.2

_______________________________________________
Freeipa-devel mailing list
Freeipa-devel@redhat.com
https://www.redhat.com/mailman/listinfo/freeipa-devel

Reply via email to