From b4e85d02812041acb7a3071070b577adaecfe5c8 Mon Sep 17 00:00:00 2001 From: Nathaniel McCallum Date: Dec 03 2014 07:48:56 +0000 Subject: Preliminary refactoring of libotp files There are no major changes in this commit other than changing filenames and symbols to have consistent namespaces. This prepares for larger changes to come in subsequent commits. Reviewed-By: Thierry Bordaz --- diff --git a/daemons/ipa-slapi-plugins/ipa-otp-lasttoken/Makefile.am b/daemons/ipa-slapi-plugins/ipa-otp-lasttoken/Makefile.am index d12fbcc..cb63409 100644 --- a/daemons/ipa-slapi-plugins/ipa-otp-lasttoken/Makefile.am +++ b/daemons/ipa-slapi-plugins/ipa-otp-lasttoken/Makefile.am @@ -3,7 +3,6 @@ PLUGIN_COMMON_DIR = ../common AM_CPPFLAGS = \ -I. \ -I$(srcdir) \ - -I$(srcdir)/../libotp \ -I$(PLUGIN_COMMON_DIR) \ -I/usr/include/dirsrv \ -DPREFIX=\""$(prefix)"\" \ diff --git a/daemons/ipa-slapi-plugins/ipa-otp-lasttoken/ipa_otp_lasttoken.c b/daemons/ipa-slapi-plugins/ipa-otp-lasttoken/ipa_otp_lasttoken.c index 15b404d..19217ba 100644 --- a/daemons/ipa-slapi-plugins/ipa-otp-lasttoken/ipa_otp_lasttoken.c +++ b/daemons/ipa-slapi-plugins/ipa-otp-lasttoken/ipa_otp_lasttoken.c @@ -41,7 +41,7 @@ # include #endif -#include +#include "../libotp/otp_token.h" #include #include "util.h" @@ -61,7 +61,7 @@ target_is_only_enabled_token(Slapi_PBlock *pb) { Slapi_DN *target_sdn = NULL; Slapi_DN *token_sdn = NULL; - struct otptoken **tokens; + struct otp_token **tokens; char *user_dn = NULL; bool match; @@ -75,10 +75,10 @@ target_is_only_enabled_token(Slapi_PBlock *pb) return false; /* Get the SDN of the only enabled token. */ - tokens = otptoken_find(plugin_id, user_dn, NULL, true, NULL); + tokens = otp_token_find(plugin_id, user_dn, NULL, true, NULL); if (tokens != NULL && tokens[0] != NULL && tokens[1] == NULL) - token_sdn = slapi_sdn_dup(otptoken_get_sdn(tokens[0])); - otptoken_free_array(tokens); + token_sdn = slapi_sdn_dup(otp_token_get_sdn(tokens[0])); + otp_token_free_array(tokens); if (token_sdn == NULL) return false; diff --git a/daemons/ipa-slapi-plugins/ipa-pwd-extop/Makefile.am b/daemons/ipa-slapi-plugins/ipa-pwd-extop/Makefile.am index 77beca2..eeb3526 100644 --- a/daemons/ipa-slapi-plugins/ipa-pwd-extop/Makefile.am +++ b/daemons/ipa-slapi-plugins/ipa-pwd-extop/Makefile.am @@ -11,7 +11,6 @@ ASN1_UTIL_DIR=../../../asn1 AM_CPPFLAGS = \ -I. \ -I$(srcdir) \ - -I$(srcdir)/../libotp \ -I$(PLUGIN_COMMON_DIR) \ -I$(KRB5_UTIL_DIR) \ -I$(ASN1_UTIL_DIR) \ diff --git a/daemons/ipa-slapi-plugins/ipa-pwd-extop/ipapwd.h b/daemons/ipa-slapi-plugins/ipa-pwd-extop/ipapwd.h index f885112..2e9d4fe 100644 --- a/daemons/ipa-slapi-plugins/ipa-pwd-extop/ipapwd.h +++ b/daemons/ipa-slapi-plugins/ipa-pwd-extop/ipapwd.h @@ -41,7 +41,7 @@ # include #endif -#include +#include "../libotp/otp_token.h" #include #include diff --git a/daemons/ipa-slapi-plugins/ipa-pwd-extop/prepost.c b/daemons/ipa-slapi-plugins/ipa-pwd-extop/prepost.c index 1f595d0..1dff6db 100644 --- a/daemons/ipa-slapi-plugins/ipa-pwd-extop/prepost.c +++ b/daemons/ipa-slapi-plugins/ipa-pwd-extop/prepost.c @@ -1140,11 +1140,11 @@ done: static bool ipapwd_do_otp_auth(const char *dn, Slapi_Entry *bind_entry, struct berval *creds) { - struct otptoken **tokens = NULL; + struct otp_token **tokens = NULL; bool success = false; /* Find all of the user's active tokens. */ - tokens = otptoken_find(ipapwd_plugin_id, dn, NULL, true, NULL); + tokens = otp_token_find(ipapwd_plugin_id, dn, NULL, true, NULL); if (tokens == NULL) { slapi_log_error(SLAPI_LOG_FATAL, IPAPWD_PLUGIN_NAME, "%s: can't find tokens for '%s'.\n", __func__, dn); @@ -1157,12 +1157,12 @@ static bool ipapwd_do_otp_auth(const char *dn, Slapi_Entry *bind_entry, /* Loop through each token. */ for (int i = 0; tokens[i] && !success; i++) { /* Attempt authentication. */ - success = otptoken_validate_berval(tokens[i], OTP_VALIDATE_STEPS, + success = otp_token_validate_berval(tokens[i], OTP_VALIDATE_STEPS, creds, true); /* Truncate the password to remove the OTP code at the end. */ if (success) { - creds->bv_len -= otptoken_get_digits(tokens[i]); + creds->bv_len -= otp_token_get_digits(tokens[i]); creds->bv_val[creds->bv_len] = '\0'; } @@ -1170,10 +1170,10 @@ static bool ipapwd_do_otp_auth(const char *dn, Slapi_Entry *bind_entry, "%s: token authentication %s " "(user: '%s', token: '%s\').\n", __func__, success ? "succeeded" : "failed", dn, - slapi_sdn_get_ndn(otptoken_get_sdn(tokens[i]))); + slapi_sdn_get_ndn(otp_token_get_sdn(tokens[i]))); } - otptoken_free_array(tokens); + otp_token_free_array(tokens); return success; } diff --git a/daemons/ipa-slapi-plugins/ipa-pwd-extop/syncreq.c b/daemons/ipa-slapi-plugins/ipa-pwd-extop/syncreq.c index cbb4536..10c49b7 100644 --- a/daemons/ipa-slapi-plugins/ipa-pwd-extop/syncreq.c +++ b/daemons/ipa-slapi-plugins/ipa-pwd-extop/syncreq.c @@ -37,8 +37,7 @@ * All rights reserved. * END COPYRIGHT BLOCK **/ - -#include +#include "../libotp/otp_token.h" #include "syncreq.h" #define OTP_SYNC_MAX_STEPS 25 @@ -56,7 +55,7 @@ bool sync_request_present(Slapi_PBlock *pb) bool sync_request_handle(Slapi_ComponentId *plugin_id, Slapi_PBlock *pb, const char *user_dn) { - struct otptoken **tokens = NULL; + struct otp_token **tokens = NULL; LDAPControl **controls = NULL; struct berval *second = NULL; struct berval *first = NULL; @@ -91,10 +90,10 @@ bool sync_request_handle(Slapi_ComponentId *plugin_id, Slapi_PBlock *pb, /* Process the synchronization. */ success = false; if (ber_scanf(ber, "}") != LBER_ERROR) { - tokens = otptoken_find(plugin_id, user_dn, token_dn, true, NULL); + tokens = otp_token_find(plugin_id, user_dn, token_dn, true, NULL); if (tokens != NULL) { - success = otptoken_sync_berval(tokens, OTP_SYNC_MAX_STEPS, first, second); - otptoken_free_array(tokens); + success = otp_token_sync_berval(tokens, OTP_SYNC_MAX_STEPS, first, second); + otp_token_free_array(tokens); } } diff --git a/daemons/ipa-slapi-plugins/libotp/Makefile.am b/daemons/ipa-slapi-plugins/libotp/Makefile.am index 6aa60c5..012c833 100644 --- a/daemons/ipa-slapi-plugins/libotp/Makefile.am +++ b/daemons/ipa-slapi-plugins/libotp/Makefile.am @@ -1,9 +1,11 @@ MAINTAINERCLEANFILES = *~ Makefile.in AM_CPPFLAGS = -I/usr/include/dirsrv -noinst_LTLIBRARIES = librfc.la libotp.la -libotp_la_LIBADD = librfc.la +noinst_LTLIBRARIES = libhotp.la libotp.la +libhotp_la_SOURCES = hotp.c hotp.h +libotp_la_SOURCES = otp_token.c otp_token.h +libotp_la_LIBADD = libhotp.la -check_PROGRAMS = t_librfc +check_PROGRAMS = t_hotp TESTS = $(check_PROGRAMS) -t_librfc_LDADD = $(NSPR_LIBS) $(NSS_LIBS) librfc.la +t_hotp_LDADD = $(NSPR_LIBS) $(NSS_LIBS) libhotp.la diff --git a/daemons/ipa-slapi-plugins/libotp/hotp.c b/daemons/ipa-slapi-plugins/libotp/hotp.c new file mode 100644 index 0000000..619bc63 --- /dev/null +++ b/daemons/ipa-slapi-plugins/libotp/hotp.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 "hotp.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/hotp.h b/daemons/ipa-slapi-plugins/libotp/hotp.h new file mode 100644 index 0000000..06ae1fd --- /dev/null +++ b/daemons/ipa-slapi-plugins/libotp/hotp.h @@ -0,0 +1,60 @@ +/** 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 **/ + +#pragma once + +#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); diff --git a/daemons/ipa-slapi-plugins/libotp/libotp.c b/daemons/ipa-slapi-plugins/libotp/libotp.c deleted file mode 100644 index c65aef0..0000000 --- a/daemons/ipa-slapi-plugins/libotp/libotp.c +++ /dev/null @@ -1,573 +0,0 @@ -/** 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 H(s) TOKEN("HOTP" s) - -#define IPA_OTP_DEFAULT_TOKEN_STEP 30 -#define IPA_OTP_OBJCLS_FILTER \ - "(|(objectClass=ipaTokenTOTP)(objectClass=ipaTokenHOTP))" - - -enum otptoken_type { - OTPTOKEN_NONE = 0, - OTPTOKEN_TOTP, - OTPTOKEN_HOTP, -}; - -struct otptoken { - Slapi_ComponentId *plugin_id; - Slapi_DN *sdn; - struct hotp_token token; - enum otptoken_type type; - union { - struct { - uint64_t watermark; - unsigned int step; - int offset; - } totp; - struct { - uint64_t counter; - } hotp; - }; -}; - -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 writeattr(const struct otptoken *token, const char *attr, - long long val) -{ - Slapi_PBlock *pb = NULL; - bool success = false; - char value[32]; - int ret; - - LDAPMod *mods[] = { - &(LDAPMod) { - LDAP_MOD_REPLACE, (char *) attr, - .mod_values = (char *[]) { value, NULL } - }, - NULL - }; - - snprintf(value, sizeof(value), "%lld", val); - - pb = slapi_pblock_new(); - slapi_modify_internal_set_pb(pb, slapi_sdn_get_dn(token->sdn), - 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; - - success = true; - -error: - slapi_pblock_destroy(pb); - return success; -} - -/** - * Validate a token. - * - * If the second token code is specified, perform synchronization. - */ -static bool validate(struct otptoken *token, time_t now, ssize_t step, - uint32_t first, const uint32_t *second) -{ - const char *attr; - uint32_t tmp; - - /* Calculate the absolute step. */ - switch (token->type) { - case OTPTOKEN_TOTP: - attr = T("watermark"); - step = (now + token->totp.offset) / token->totp.step + step; - if (token->totp.watermark > 0 && step < token->totp.watermark) - return false; - break; - case OTPTOKEN_HOTP: - if (step < 0) /* NEVER go backwards! */ - return false; - attr = H("counter"); - step = token->hotp.counter + step; - break; - default: - return false; - } - - /* Validate the first code. */ - if (!hotp(&token->token, step++, &tmp)) - return false; - - if (first != tmp) - return false; - - /* Validate the second code if specified. */ - if (second != NULL) { - if (!hotp(&token->token, step++, &tmp)) - return false; - - if (*second != tmp) - return false; - } - - /* Write the step value. */ - if (!writeattr(token, attr, step)) - return false; - - /* Save our modifications to the object. */ - switch (token->type) { - case OTPTOKEN_TOTP: - /* Perform optional synchronization steps. */ - if (second != NULL) { - tmp = (step - now / token->totp.step) * token->totp.step; - if (!writeattr(token, T("clockOffset"), tmp)) - return false; - token->totp.offset = tmp; - } - token->totp.watermark = step; - break; - case OTPTOKEN_HOTP: - token->hotp.counter = step; - break; - default: - break; - } - - return true; -} - - -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; - else if (strcasecmp(vals[i], "ipaTokenHOTP") == 0) - token->type = OTPTOKEN_HOTP; - } - 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 watermark. */ - token->totp.watermark = slapi_entry_attr_get_int(entry, T("watermark")); - - /* 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; - case OTPTOKEN_HOTP: - /* Get counter. */ - token->hotp.counter = slapi_entry_attr_get_int(entry, H("counter")); - 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; - const char *basedn = 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; - - basedn = get_basedn(sdn); - if (basedn == NULL) - goto error; - - /* Find all user tokens. */ - slapi_search_internal_set_pb(pb, basedn, - 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; -} - -static 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 true; - - /* Validate the negative step. */ - if (validate(token, now, 0 - i, code, NULL)) - return true; - } - - return false; -} - - -/* - * Convert code berval to decimal. - * - * NOTE: We can't use atol() or strtoul() because: - * 1. If we have leading zeros, atol() fails. - * 2. Neither support limiting conversion by length. - */ -static bool bvtod(const struct berval *code, uint32_t *out) -{ - *out = 0; - - for (ber_len_t i = 0; i < code->bv_len; i++) { - if (code->bv_val[i] < '0' || code->bv_val[i] > '9') - return false; - *out *= 10; - *out += code->bv_val[i] - '0'; - } - - return code->bv_len != 0; -} - -bool otptoken_validate_berval(struct otptoken *token, size_t steps, - const struct berval *code, bool tail) -{ - struct berval tmp; - uint32_t otp; - - if (token == NULL || code == NULL) - return false; - tmp = *code; - - if (tmp.bv_len < token->token.digits) - return false; - - if (tail) - tmp.bv_val = &tmp.bv_val[tmp.bv_len - token->token.digits]; - tmp.bv_len = token->token.digits; - - if (!bvtod(&tmp, &otp)) - return false; - - return otptoken_validate(token, steps, otp); -} - -static 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 true; - - /* Validate the negative step. */ - if (validate(tokens[j], now, 0 - i, first_code, &second_code)) - return true; - } - } - - return false; -} - -bool otptoken_sync_berval(struct otptoken * const *tokens, size_t steps, - const struct berval *first_code, - const struct berval *second_code) -{ - uint32_t second = 0; - uint32_t first = 0; - - if (!bvtod(first_code, &first)) - return false; - - if (!bvtod(second_code, &second)) - return false; - - return otptoken_sync(tokens, steps, first, second); -} diff --git a/daemons/ipa-slapi-plugins/libotp/libotp.h b/daemons/ipa-slapi-plugins/libotp/libotp.h deleted file mode 100644 index 24915f8..0000000 --- a/daemons/ipa-slapi-plugins/libotp/libotp.h +++ /dev/null @@ -1,93 +0,0 @@ -/** 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. If tail is true, - * it will be assumed that the token is specified at the end of the string. */ -bool otptoken_validate_berval(struct otptoken *token, size_t steps, - const struct berval *code, bool tail); - -/* Synchronize the token within a range of steps. */ -bool otptoken_sync_berval(struct otptoken * const *tokens, size_t steps, - const struct berval *first_code, - const struct berval *second_code); - -#endif /* LIBOTP_H_ */ diff --git a/daemons/ipa-slapi-plugins/libotp/librfc.c b/daemons/ipa-slapi-plugins/libotp/librfc.c deleted file mode 100644 index d74820e..0000000 --- a/daemons/ipa-slapi-plugins/libotp/librfc.c +++ /dev/null @@ -1,170 +0,0 @@ -/** 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 deleted file mode 100644 index 04b1176..0000000 --- a/daemons/ipa-slapi-plugins/libotp/librfc.h +++ /dev/null @@ -1,63 +0,0 @@ -/** 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/otp_token.c b/daemons/ipa-slapi-plugins/libotp/otp_token.c new file mode 100644 index 0000000..7860c8a --- /dev/null +++ b/daemons/ipa-slapi-plugins/libotp/otp_token.c @@ -0,0 +1,572 @@ +/** 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 "otp_token.h" +#include "hotp.h" + +#include +#include + +#define TOKEN(s) "ipaToken" s +#define O(s) TOKEN("OTP" s) +#define T(s) TOKEN("TOTP" s) +#define H(s) TOKEN("HOTP" s) + +#define IPA_OTP_DEFAULT_TOKEN_STEP 30 +#define IPA_OTP_OBJCLS_FILTER \ + "(|(objectClass=ipaTokenTOTP)(objectClass=ipaTokenHOTP))" + +enum type { + TYPE_NONE = 0, + TYPE_TOTP, + TYPE_HOTP, +}; + +struct otp_token { + Slapi_ComponentId *plugin_id; + Slapi_DN *sdn; + struct hotp_token token; + enum type type; + union { + struct { + uint64_t watermark; + unsigned int step; + int offset; + } totp; + struct { + uint64_t counter; + } hotp; + }; +}; + +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 writeattr(const struct otp_token *token, const char *attr, + long long val) +{ + Slapi_PBlock *pb = NULL; + bool success = false; + char value[32]; + int ret; + + LDAPMod *mods[] = { + &(LDAPMod) { + LDAP_MOD_REPLACE, (char *) attr, + .mod_values = (char *[]) { value, NULL } + }, + NULL + }; + + snprintf(value, sizeof(value), "%lld", val); + + pb = slapi_pblock_new(); + slapi_modify_internal_set_pb(pb, slapi_sdn_get_dn(token->sdn), + 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; + + success = true; + +error: + slapi_pblock_destroy(pb); + return success; +} + +/** + * Validate a token. + * + * If the second token code is specified, perform synchronization. + */ +static bool validate(struct otp_token *token, time_t now, ssize_t step, + uint32_t first, const uint32_t *second) +{ + const char *attr; + uint32_t tmp; + + /* Calculate the absolute step. */ + switch (token->type) { + case TYPE_TOTP: + attr = T("watermark"); + step = (now + token->totp.offset) / token->totp.step + step; + if (token->totp.watermark > 0 && step < token->totp.watermark) + return false; + break; + case TYPE_HOTP: + if (step < 0) /* NEVER go backwards! */ + return false; + attr = H("counter"); + step = token->hotp.counter + step; + break; + default: + return false; + } + + /* Validate the first code. */ + if (!hotp(&token->token, step++, &tmp)) + return false; + + if (first != tmp) + return false; + + /* Validate the second code if specified. */ + if (second != NULL) { + if (!hotp(&token->token, step++, &tmp)) + return false; + + if (*second != tmp) + return false; + } + + /* Write the step value. */ + if (!writeattr(token, attr, step)) + return false; + + /* Save our modifications to the object. */ + switch (token->type) { + case TYPE_TOTP: + /* Perform optional synchronization steps. */ + if (second != NULL) { + tmp = (step - now / token->totp.step) * token->totp.step; + if (!writeattr(token, T("clockOffset"), tmp)) + return false; + token->totp.offset = tmp; + } + token->totp.watermark = step; + break; + case TYPE_HOTP: + token->hotp.counter = step; + break; + default: + break; + } + + return true; +} + +static void otp_token_free(struct otp_token *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 otp_token_free_array(struct otp_token **tokens) +{ + if (tokens == NULL) + return; + + for (size_t i = 0; tokens[i] != NULL; i++) + otp_token_free(tokens[i]); + + free(tokens); +} + +static struct otp_token *otp_token_new(Slapi_ComponentId *id, + Slapi_Entry *entry) +{ + const struct berval *tmp; + struct otp_token *token; + char **vals; + + token = calloc(1, sizeof(struct otp_token)); + 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 = TYPE_NONE; + for (int i = 0; vals[i] != NULL; i++) { + if (strcasecmp(vals[i], "ipaTokenTOTP") == 0) + token->type = TYPE_TOTP; + else if (strcasecmp(vals[i], "ipaTokenHOTP") == 0) + token->type = TYPE_HOTP; + } + slapi_ch_array_free(vals); + if (token->type == TYPE_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 TYPE_TOTP: + /* Get offset. */ + token->totp.offset = slapi_entry_attr_get_int(entry, T("clockOffset")); + + /* Get watermark. */ + token->totp.watermark = slapi_entry_attr_get_int(entry, T("watermark")); + + /* 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; + case TYPE_HOTP: + /* Get counter. */ + token->hotp.counter = slapi_entry_attr_get_int(entry, H("counter")); + break; + default: + break; + } + + return token; + +error: + otp_token_free(token); + return NULL; +} + +static struct otp_token **find(Slapi_ComponentId *id, const char *user_dn, + const char *token_dn, const char *intfilter, + const char *extfilter) +{ + struct otp_token **tokens = NULL; + Slapi_Entry **entries = NULL; + Slapi_PBlock *pb = NULL; + Slapi_DN *sdn = NULL; + char *filter = NULL; + const char *basedn = 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; + + basedn = get_basedn(sdn); + if (basedn == NULL) + goto error; + + /* Find all user tokens. */ + slapi_search_internal_set_pb(pb, basedn, + 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] = otp_token_new(id, entries[count]); + if (tokens[count] == NULL) { + otp_token_free_array(tokens); + tokens = NULL; + goto error; + } + } + +error: + if (sdn != NULL) + slapi_sdn_free(&sdn); + slapi_pblock_destroy(pb); + return tokens; +} + +struct otp_token ** +otp_token_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 otp_token_get_digits(struct otp_token *token) +{ + return token == NULL ? 0 : token->token.digits; +} + +const Slapi_DN *otp_token_get_sdn(struct otp_token *token) +{ + return token->sdn; +} + +static bool otp_token_validate(struct otp_token *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 == TYPE_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 true; + + /* Validate the negative step. */ + if (validate(token, now, 0 - i, code, NULL)) + return true; + } + + return false; +} + + +/* + * Convert code berval to decimal. + * + * NOTE: We can't use atol() or strtoul() because: + * 1. If we have leading zeros, atol() fails. + * 2. Neither support limiting conversion by length. + */ +static bool bvtod(const struct berval *code, uint32_t *out) +{ + *out = 0; + + for (ber_len_t i = 0; i < code->bv_len; i++) { + if (code->bv_val[i] < '0' || code->bv_val[i] > '9') + return false; + *out *= 10; + *out += code->bv_val[i] - '0'; + } + + return code->bv_len != 0; +} + +bool otp_token_validate_berval(struct otp_token *token, size_t steps, + const struct berval *code, bool tail) +{ + struct berval tmp; + uint32_t otp; + + if (token == NULL || code == NULL) + return false; + tmp = *code; + + if (tmp.bv_len < token->token.digits) + return false; + + if (tail) + tmp.bv_val = &tmp.bv_val[tmp.bv_len - token->token.digits]; + tmp.bv_len = token->token.digits; + + if (!bvtod(&tmp, &otp)) + return false; + + return otp_token_validate(token, steps, otp); +} + +static bool otp_token_sync(struct otp_token * 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 true; + + /* Validate the negative step. */ + if (validate(tokens[j], now, 0 - i, first_code, &second_code)) + return true; + } + } + + return false; +} + +bool otp_token_sync_berval(struct otp_token * const *tokens, size_t steps, + const struct berval *first_code, + const struct berval *second_code) +{ + uint32_t second = 0; + uint32_t first = 0; + + if (!bvtod(first_code, &first)) + return false; + + if (!bvtod(second_code, &second)) + return false; + + return otp_token_sync(tokens, steps, first, second); +} diff --git a/daemons/ipa-slapi-plugins/libotp/otp_token.h b/daemons/ipa-slapi-plugins/libotp/otp_token.h new file mode 100644 index 0000000..2f33678 --- /dev/null +++ b/daemons/ipa-slapi-plugins/libotp/otp_token.h @@ -0,0 +1,87 @@ +/** 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 **/ + +#pragma once + +#include +#include +#include + +struct otp_token; + +/* Frees the token array. */ +void otp_token_free_array(struct otp_token **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 otp_token **otp_token_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 otp_token_get_digits(struct otp_token *token); + +/* Get the SDN of the token. */ +const Slapi_DN *otp_token_get_sdn(struct otp_token *token); + +/* 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 otp_token_validate_berval(struct otp_token *token, size_t steps, + const struct berval *code, bool tail); + +/* Synchronize the token within a range of steps. */ +bool otp_token_sync_berval(struct otp_token * const *tokens, size_t steps, + const struct berval *first_code, + const struct berval *second_code); + diff --git a/daemons/ipa-slapi-plugins/libotp/t_hotp.c b/daemons/ipa-slapi-plugins/libotp/t_hotp.c new file mode 100644 index 0000000..2e995fd --- /dev/null +++ b/daemons/ipa-slapi-plugins/libotp/t_hotp.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 "hotp.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; +} diff --git a/daemons/ipa-slapi-plugins/libotp/t_librfc.c b/daemons/ipa-slapi-plugins/libotp/t_librfc.c deleted file mode 100644 index f7eab7f..0000000 --- a/daemons/ipa-slapi-plugins/libotp/t_librfc.c +++ /dev/null @@ -1,121 +0,0 @@ -/** 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; -}