From 5b58348cd316dd817672cb81358ed557c28e09d3 Mon Sep 17 00:00:00 2001 From: Nathaniel McCallum Date: Tue, 16 Apr 2013 16:00:09 -0400 Subject: Add OTP support to ipa-pwd-extop During LDAP bind, this now plugin determines if a user is enabled for OTP authentication. If so, then the OTP is validated in addition to the password. This allows 2FA during user binds. https://fedorahosted.org/freeipa/ticket/3367 http://freeipa.org/page/V3/OTP --- daemons/configure.ac | 39 +- .../ipa-slapi-plugins/ipa-pwd-extop/Makefile.am | 42 ++- daemons/ipa-slapi-plugins/ipa-pwd-extop/auth.c | 398 +++++++++++++++++++++ daemons/ipa-slapi-plugins/ipa-pwd-extop/common.c | 130 +++++++ .../ipa-pwd-extop/ipa_pwd_extop.c | 109 +++++- daemons/ipa-slapi-plugins/ipa-pwd-extop/ipapwd.h | 36 ++ daemons/ipa-slapi-plugins/ipa-pwd-extop/otp.c | 180 ++++++++++ daemons/ipa-slapi-plugins/ipa-pwd-extop/prepost.c | 307 +++++++++++++++- daemons/ipa-slapi-plugins/ipa-pwd-extop/t_hotp.c | 82 +++++ daemons/ipa-slapi-plugins/ipa-pwd-extop/t_totp.c | 103 ++++++ 10 files changed, 1368 insertions(+), 58 deletions(-) create mode 100644 daemons/ipa-slapi-plugins/ipa-pwd-extop/auth.c create mode 100644 daemons/ipa-slapi-plugins/ipa-pwd-extop/otp.c create mode 100644 daemons/ipa-slapi-plugins/ipa-pwd-extop/t_hotp.c create mode 100644 daemons/ipa-slapi-plugins/ipa-pwd-extop/t_totp.c (limited to 'daemons') diff --git a/daemons/configure.ac b/daemons/configure.ac index 371c28d04..21d4e7a77 100644 --- a/daemons/configure.ac +++ b/daemons/configure.ac @@ -22,37 +22,10 @@ AM_CONDITIONAL([HAVE_GCC], [test "$ac_cv_prog_gcc" = yes]) AC_SUBST(VERSION) dnl --------------------------------------------------------------------------- -dnl - Check for NSPR +dnl - Check for NSPR/NSS dnl --------------------------------------------------------------------------- -AC_CHECK_HEADER(nspr4/nspr.h) -AC_CHECK_HEADER(nspr/nspr.h) -if test "x$ac_cv_header_nspr4_nspr_h" = "xno" && test "x$ac_cv_header_nspr_nspr_h" = "xno" ; then - AC_MSG_ERROR([Required NSPR header not available (nspr-devel)]) -fi -if test "x$ac_cv_header_nspr4_nspr_h" = "xyes" ; then - NSPR4="-I/usr/include/nspr4" -fi -if test "x$ac_cv_header_nspr_nspr_h" = "xyes" ; then - NSPR4="-I/usr/include/nspr" -fi - -dnl --------------------------------------------------------------------------- -dnl - Check for NSS -dnl --------------------------------------------------------------------------- -SAVE_CPPFLAGS=$CPPFLAGS -CPPFLAGS=$NSPR4 -AC_CHECK_HEADER(nss3/nss.h) -AC_CHECK_HEADER(nss/nss.h) -CPPFLAGS=$SAVE_CPPFLAGS -if test "x$ac_cv_header_nss3_nss_h" = "xno" && test "x$ac_cv_header_nss_nss_h" = "xno" ; then - AC_MSG_ERROR([Required NSS header not available (nss-devel)]) -fi -if test "x$ac_cv_header_nss3_nss_h" = "xyes" ; then - NSS3="-I/usr/include/nss3" -fi -if test "x$ac_cv_header_nss_nss_h" = "xyes" ; then - NSS3="-I/usr/include/nss" -fi +PKG_CHECK_MODULES([NSPR], [nspr], [], [AC_MSG_ERROR([libnspr not found])]) +PKG_CHECK_MODULES([NSS], [nss], [], [AC_MSG_ERROR([libnss not found])]) dnl --------------------------------------------------------------------------- dnl - Check for DS slapi plugin @@ -60,7 +33,7 @@ dnl --------------------------------------------------------------------------- # Need to hack CPPFLAGS to be able to correctly detetct slapi-plugin.h SAVE_CPPFLAGS=$CPPFLAGS -CPPFLAGS=$NSPR4 +CPPFLAGS=$NSPR_CFLAGS AC_CHECK_HEADER(dirsrv/slapi-plugin.h) if test "x$ac_cv_header_dirsrv_slapi-plugin_h" = "xno" ; then AC_MSG_ERROR([Required 389-ds header not available (389-ds-base-devel)]) @@ -96,7 +69,7 @@ dnl - Check for Mozilla LDAP and OpenLDAP SDK dnl --------------------------------------------------------------------------- SAVE_CPPFLAGS=$CPPFLAGS -CPPFLAGS="$NSPR4 $NSS3" +CPPFLAGS="$NSPR_CFLAGS $NSS_CFLAGS" AC_CHECK_HEADER(svrcore.h) AC_CHECK_HEADER(svrcore/svrcore.h) if test "x$ac_cv_header_svrcore_h" = "xno" && test "x$ac_cv_header_svrcore_svrcore_h" = "xno" ; then @@ -144,7 +117,7 @@ AC_ARG_WITH([openldap], [compile plugins with openldap instead of mozldap])], [], []) -LDAP_CFLAGS="${OPENLDAP_CFLAGS} $NSPR4 $NSS3 -DUSE_OPENLDAP" +LDAP_CFLAGS="${OPENLDAP_CFLAGS} $NSPR_CFLAGS $NSS_CFLAGS -DUSE_OPENLDAP" LDAP_LIBS="${OPENLDAP_LIBS}" AC_DEFINE_UNQUOTED(WITH_OPENLDAP, 1, [Use OpenLDAP libraries]) diff --git a/daemons/ipa-slapi-plugins/ipa-pwd-extop/Makefile.am b/daemons/ipa-slapi-plugins/ipa-pwd-extop/Makefile.am index 90f940fd3..b53b2e1e4 100644 --- a/daemons/ipa-slapi-plugins/ipa-pwd-extop/Makefile.am +++ b/daemons/ipa-slapi-plugins/ipa-pwd-extop/Makefile.am @@ -1,7 +1,8 @@ NULL = -PLUGIN_COMMON_DIR=../common -KRB5_UTIL_DIR= ../../../util +MAINTAINERCLEANFILES = *~ Makefile.in +PLUGIN_COMMON_DIR = ../common +KRB5_UTIL_DIR = ../../../util KRB5_UTIL_SRCS = $(KRB5_UTIL_DIR)/ipa_krb5.c \ $(KRB5_UTIL_DIR)/ipa_pwd.c \ $(KRB5_UTIL_DIR)/ipa_pwd_ntlm.c @@ -23,13 +24,30 @@ AM_CPPFLAGS = \ $(SSL_CFLAGS) \ $(WARN_CFLAGS) \ $(NULL) + +AM_LDFLAGS = \ + $(KRB5_LIBS) \ + $(SSL_LIBS) \ + $(LDAP_LIBS) \ + $(NSPR_LIBS) \ + $(NSS_LIBS) \ + -avoid-version \ + -export-symbols-regex ^ipapwd_init$ -plugindir = $(libdir)/dirsrv/plugins -plugin_LTLIBRARIES = \ - libipa_pwd_extop.la \ - $(NULL) +# OTP Convenience Library and Tests +noinst_LTLIBRARIES = libotp.la +libotp_la_SOURCES = otp.c +check_PROGRAMS = t_hotp t_totp +t_hotp_LDADD = libotp.la +t_totp_LDADD = libotp.la +TESTS = $(check_PROGRAMS) +# Plugin Binary +plugindir = $(libdir)/dirsrv/plugins +plugin_LTLIBRARIES = libipa_pwd_extop.la +libipa_pwd_extop_la_LIBADD = libotp.la libipa_pwd_extop_la_SOURCES = \ + auth.c \ common.c \ encoding.c \ prepost.c \ @@ -37,14 +55,6 @@ libipa_pwd_extop_la_SOURCES = \ $(KRB5_UTIL_SRCS) \ $(NULL) -libipa_pwd_extop_la_LDFLAGS = -avoid-version - -libipa_pwd_extop_la_LIBADD = \ - $(KRB5_LIBS) \ - $(SSL_LIBS) \ - $(LDAP_LIBS) \ - $(NULL) - appdir = $(IPA_DATA_DIR) app_DATA = \ pwd-extop-conf.ldif \ @@ -55,6 +65,4 @@ EXTRA_DIST = \ $(app_DATA) \ $(NULL) -MAINTAINERCLEANFILES = \ - *~ \ - Makefile.in + diff --git a/daemons/ipa-slapi-plugins/ipa-pwd-extop/auth.c b/daemons/ipa-slapi-plugins/ipa-pwd-extop/auth.c new file mode 100644 index 000000000..ae47bab33 --- /dev/null +++ b/daemons/ipa-slapi-plugins/ipa-pwd-extop/auth.c @@ -0,0 +1,398 @@ +/** 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 "ipapwd.h" + +#define IPA_OTP_TOKEN_TOTP_OC "ipaTokenTOTP" +#define IPA_OTP_DEFAULT_TOKEN_ALGORITHM "sha1" +#define IPA_OTP_DEFAULT_TOKEN_OFFSET 0 +#define IPA_OTP_DEFAULT_TOKEN_STEP 30 + +/* + * From otp.c + */ +bool ipapwd_hotp(const uint8_t *key, size_t len, const char *algo, int digits, + uint64_t counter, uint32_t *out); + +bool ipapwd_totp(const uint8_t *key, size_t len, const char *algo, int digits, + time_t time, int offset, unsigned int step, uint32_t *out); + +/* From ipa_pwd_extop.c */ +extern void *ipapwd_plugin_id; + +/* Data types. */ +struct token { + struct { + uint8_t *data; + size_t len; + } key; + char *algo; + int len; + union { + struct { + uint64_t counter; + } hotp; + struct { + unsigned int step; + int offset; + } totp; + }; + bool (*auth)(const struct token *token, uint32_t otp); +}; + +struct credentials { + struct token token; + Slapi_Value *ltp; + uint32_t otp; +}; + +static const char *valid_algos[] = { "sha1", "sha256", "sha384", + "sha512", NULL }; + +static inline bool is_algo_valid(const char *algo) +{ + 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); +} + +/* Authenticate a totp token. Return zero on success. */ +static bool auth_totp(const struct token *token, uint32_t otp) +{ + time_t times[5]; + uint32_t val; + int i; + + /* Get the token value for now and two steps in either direction. */ + times[0] = time(NULL); + times[1] = times[0] + token->totp.step * 1; + times[2] = times[0] - token->totp.step * 1; + times[3] = times[0] + token->totp.step * 2; + times[4] = times[0] - token->totp.step * 2; + if (times[0] == -1) + return false; + + /* Check all the times for a match. */ + for (i = 0; i < sizeof(times) / sizeof(times[0]); i++) { + if (!ipapwd_totp(token->key.data, token->key.len, token->algo, + token->len, times[i], token->totp.offset, + token->totp.step, &val)) { + return false; + } + + if (val == otp) { + return true; + } + } + + return false; +} + +static void token_free_contents(struct token *token) +{ + if (token == NULL) + return; + + slapi_ch_free_string(&token->algo); + slapi_ch_free((void **) &token->key.data); +} + +/* Decode an OTP token entry. Return zero on success. */ +static bool token_decode(Slapi_Entry *te, struct token *token) +{ + const struct berval *tmp; + + /* Get key. */ + tmp = entry_attr_get_berval(te, IPA_OTP_TOKEN_KEY_TYPE); + if (tmp == NULL) { + slapi_log_error(SLAPI_LOG_PLUGIN, IPAPWD_PLUGIN_NAME, + "token_decode: key not set for token \"%s\".\n", + slapi_entry_get_ndn(te)); + return false; + } + token->key.len = tmp->bv_len; + token->key.data = (void *) slapi_ch_malloc(token->key.len); + memcpy(token->key.data, tmp->bv_val, token->key.len); + + /* Get length. */ + token->len = slapi_entry_attr_get_int(te, IPA_OTP_TOKEN_LENGTH_TYPE); + if (token->len < 6 || token->len > 10) { + slapi_log_error(SLAPI_LOG_PLUGIN, IPAPWD_PLUGIN_NAME, + "token_decode: %s is not defined or invalid " + "for token \"%s\".\n", IPA_OTP_TOKEN_LENGTH_TYPE, + slapi_entry_get_ndn(te)); + token_free_contents(token); + return false; + } + + /* Get algorithm. */ + token->algo = slapi_entry_attr_get_charptr(te, + IPA_OTP_TOKEN_ALGORITHM_TYPE); + if (token->algo == NULL) + token->algo = slapi_ch_strdup(IPA_OTP_DEFAULT_TOKEN_ALGORITHM); + if (!is_algo_valid(token->algo)) { + slapi_log_error(SLAPI_LOG_PLUGIN, IPAPWD_PLUGIN_NAME, + "token_decode: invalid token algorithm " + "specified for token \"%s\".\n", + slapi_entry_get_ndn(te)); + token_free_contents(token); + return false; + } + + /* Currently, we only support TOTP. */ + token->auth = auth_totp; + + /* Get offset. */ + token->totp.offset = slapi_entry_attr_get_int(te, + IPA_OTP_TOKEN_OFFSET_TYPE); + if (token->totp.offset == 0) + token->totp.offset = IPA_OTP_DEFAULT_TOKEN_OFFSET; + + /* Get step. */ + token->totp.step = slapi_entry_attr_get_uint(te, IPA_OTP_TOKEN_STEP_TYPE); + if (token->totp.step == 0) + token->totp.step = IPA_OTP_DEFAULT_TOKEN_STEP; + + return true; +} + +static void credentials_free_contents(struct credentials *credentials) +{ + if (!credentials) + return; + + token_free_contents(&credentials->token); + slapi_value_free(&credentials->ltp); +} + +/* Parse credentials and token entry. Return zero on success. */ +static bool credentials_parse(Slapi_Entry *te, struct berval *creds, + struct credentials *credentials) +{ + char *tmp; + int len; + + if (!token_decode(te, &credentials->token)) + return false; + + /* Is the credential too short? If so, error. */ + if (credentials->token.len >= creds->bv_len) { + slapi_log_error(SLAPI_LOG_PLUGIN, IPAPWD_PLUGIN_NAME, + "credentials_parse: supplied credential is less " + "than or equal to %s for token \"%s\".\n", + IPA_OTP_TOKEN_LENGTH_TYPE, slapi_entry_get_ndn(te)); + token_free_contents(&credentials->token); + return false; + } + + /* Extract the password from the supplied credential. We hand the + * memory off to a Slapi_Value, so we don't want to directly free the + * string. */ + len = creds->bv_len - credentials->token.len; + tmp = slapi_ch_calloc(len + 1, sizeof(char)); + strncpy(tmp, creds->bv_val, len); + credentials->ltp = slapi_value_new_string_passin(tmp); + + /* Extract the token value as a (minimum) 32-bit unsigned integer. */ + tmp = slapi_ch_calloc(credentials->token.len + 1, sizeof(char)); + strncpy(tmp, creds->bv_val + len, credentials->token.len); + credentials->otp = strtoul(tmp, NULL, 10); + slapi_ch_free_string(&tmp); + + return true; +} + +/* + * Attempts to perform OTP authentication for the passed in bind entry using + * the passed in credentials. + */ +bool ipapwd_do_otp_auth(Slapi_Entry *bind_entry, struct berval *creds) +{ + Slapi_PBlock *search_pb = NULL; + Slapi_Value **pwd_vals = NULL; + Slapi_Attr *pwd_attr = NULL; + Slapi_Entry **tokens = NULL; + Slapi_DN *base_sdn = NULL; + Slapi_Backend *be = NULL; + char *user_dn = NULL; + char *filter = NULL; + int pwd_numvals = 0; + bool ret = false; + int result = 0; + int hint = 0; + int i = 0; + + search_pb = slapi_pblock_new(); + + /* Fetch the user DN. */ + user_dn = slapi_entry_get_ndn(bind_entry); + if (user_dn == NULL) { + slapi_log_error(SLAPI_LOG_FATAL, IPAPWD_PLUGIN_NAME, + "ipapwd_do_otp_auth: error retrieving bind DN.\n"); + goto done; + } + + /* Search for TOTP tokens associated with this user. We search for + * tokens who list this user as the owner in the same backend where + * the user entry is located. */ + filter = slapi_ch_smprintf("(&(%s=%s)(%s=%s))", SLAPI_ATTR_OBJECTCLASS, + IPA_OTP_TOKEN_TOTP_OC, IPA_OTP_TOKEN_OWNER_TYPE, + user_dn); + + be = slapi_be_select(slapi_entry_get_sdn(bind_entry)); + if (be != NULL) { + base_sdn = (Slapi_DN *) slapi_be_getsuffix(be, 0); + } + if (base_sdn == NULL) { + slapi_log_error(SLAPI_LOG_FATAL, IPAPWD_PLUGIN_NAME, + "ipapwd_do_otp_auth: error determining the search " + "base for user \"%s\".\n", + user_dn); + } + + slapi_search_internal_set_pb(search_pb, slapi_sdn_get_ndn(base_sdn), + LDAP_SCOPE_SUBTREE, filter, NULL, 0, NULL, + NULL, ipapwd_plugin_id, 0); + + slapi_search_internal_pb(search_pb); + slapi_pblock_get(search_pb, SLAPI_PLUGIN_INTOP_RESULT, &result); + + if (LDAP_SUCCESS != result) { + slapi_log_error(SLAPI_LOG_FATAL, IPAPWD_PLUGIN_NAME, + "ipapwd_do_otp_auth: error searching for tokens " + "associated with user \"%s\" (err=%d).\n", + user_dn, result); + goto done; + } + + slapi_pblock_get(search_pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &tokens); + + if (tokens == NULL) { + /* This user has no associated tokens, so just bail out. */ + goto done; + } + + /* Fetch the userPassword values so we can perform the password checks + * when processing tokens below. */ + if (slapi_entry_attr_find(bind_entry, SLAPI_USERPWD_ATTR, &pwd_attr) != 0 || + slapi_attr_get_numvalues(pwd_attr, &pwd_numvals) != 0) { + slapi_log_error(SLAPI_LOG_PLUGIN, IPAPWD_PLUGIN_NAME, + "ipapwd_do_otp_auth: no passwords are set for user " + "\"%s\".\n", user_dn); + goto done; + } + + /* We need to create a Slapi_Value array of the present password values + * for the compare function. There's no nicer way of doing this. */ + pwd_vals = (Slapi_Value **) slapi_ch_calloc(pwd_numvals, + sizeof(Slapi_Value *)); + + for (hint = slapi_attr_first_value(pwd_attr, &pwd_vals[i]); hint != -1; + hint = slapi_attr_next_value(pwd_attr, hint, &pwd_vals[i])) { + ++i; + } + + /* Loop through each token and attempt to authenticate. */ + for (i = 0; tokens && tokens[i]; i++) { + struct credentials credentials; + + /* Parse the token entry and the credentials. */ + if (!credentials_parse(tokens[i], creds, &credentials)) + continue; + + /* Check if the password portion of the credential is correct. */ + i = slapi_pw_find_sv(pwd_vals, credentials.ltp); + if (i != 0) { + slapi_log_error(SLAPI_LOG_PLUGIN, IPAPWD_PLUGIN_NAME, + "ipapwd_do_otp_auth: password check failed when " + "processing token \"%s\" for user \"%s\".\n", + slapi_entry_get_ndn(tokens[i]), user_dn); + credentials_free_contents(&credentials); + continue; + } + + /* Attempt to perform OTP authentication for this token. */ + if (!credentials.token.auth(&credentials.token, credentials.otp)) { + slapi_log_error(SLAPI_LOG_PLUGIN, IPAPWD_PLUGIN_NAME, + "ipapwd_do_otp_auth: OTP auth failed when " + "processing token \"%s\" for user \"%s\".\n", + slapi_entry_get_ndn(tokens[i]), user_dn); + credentials_free_contents(&credentials); + continue; + } + + /* Authentication successful! */ + credentials_free_contents(&credentials); + slapi_log_error(SLAPI_LOG_PLUGIN, IPAPWD_PLUGIN_NAME, + "ipapwd_do_otp_auth: successfully " + "authenticated user \"%s\" using token " + "\"%s\".\n", + user_dn, slapi_entry_get_ndn(tokens[i])); + ret = true; + break; + } + +done: + slapi_ch_free_string(&filter); + slapi_free_search_results_internal(search_pb); + slapi_pblock_destroy(search_pb); + return ret; +} diff --git a/daemons/ipa-slapi-plugins/ipa-pwd-extop/common.c b/daemons/ipa-slapi-plugins/ipa-pwd-extop/common.c index bb1d96ade..a54e91d87 100644 --- a/daemons/ipa-slapi-plugins/ipa-pwd-extop/common.c +++ b/daemons/ipa-slapi-plugins/ipa-pwd-extop/common.c @@ -40,6 +40,9 @@ #include "ipapwd.h" #include "util.h" +/* Attribute defines */ +#define IPA_OTP_USER_AUTH_TYPE "ipaUserAuthType" + /* Type of connection for this operation;*/ #define LDAP_EXTOP_PASSMOD_CONN_SECURE @@ -67,6 +70,133 @@ static const char *ipapwd_def_encsalts[] = { NULL }; +static PRInt32 g_allowed_auth_types = 0; + +/* + * Checks if an authentication type is allowed. A NULL terminated + * list of allowed auth type values is passed in along with the flag + * for the auth type you are inquiring about. If auth_type_list is + * NULL, the global config will be consulted. + */ +bool ipapwd_is_auth_type_allowed(char **auth_type_list, int auth_type) +{ + char *auth_type_value = NULL; + int i = 0; + + /* Get the string value for the authentication type we are checking for. */ + switch (auth_type) { + case IPA_OTP_AUTH_TYPE_OTP: + auth_type_value = IPA_OTP_AUTH_TYPE_VALUE_OTP; + break; + case IPA_OTP_AUTH_TYPE_PASSWORD: + auth_type_value = IPA_OTP_AUTH_TYPE_VALUE_PASSWORD; + break; + case IPA_OTP_AUTH_TYPE_PKINIT: + auth_type_value = IPA_OTP_AUTH_TYPE_VALUE_PKINIT; + break; + default: /* Unknown type.*/ + return false; + } + + if (auth_type_list == NULL) { + /* Check if the requested authentication type is in the global list. */ + PRInt32 auth_type_flags; + + /* Do an atomic read of the allowed auth types bit field. */ + auth_type_flags = PR_ATOMIC_ADD(&g_allowed_auth_types, 0); + + /* Check if the flag for the desired auth type is set. */ + return auth_type_flags & auth_type; + } + + /* Check if the requested authentication type is in the user list. */ + for (i = 0; auth_type_list[i]; i++) { + if (strcasecmp(auth_type_list[i], auth_type_value) == 0) { + return true; + } + } + + return false; +} + +/* + * Parses and validates an OTP config entry. If apply is non-zero, then + * we will load and start using the new config. You can simply + * validate config without making any changes by setting apply to false. + */ +bool ipapwd_parse_otp_config_entry(Slapi_Entry * e, bool apply) +{ + PRInt32 allowed_auth_types = 0; + PRInt32 default_auth_types = 0; + char **auth_types = NULL; + + /* If no auth types are set, we default to only allowing password + * authentication. Other authentication types can be allowed at the + * user level. */ + default_auth_types |= IPA_OTP_AUTH_TYPE_PASSWORD; + + if (e == NULL) { + /* There is no config entry, so just set the defaults. */ + allowed_auth_types = default_auth_types; + goto done; + } + + /* Parse and validate the config entry. We currently tolerate invalid + * config settings, so there is no real validation performed. We will + * likely want to reject invalid config as we expand the plug-in + * functionality, so the validation logic is here for us to use later. */ + + /* Fetch the auth type values from the config entry. */ + auth_types = slapi_entry_attr_get_charray(e, IPA_OTP_USER_AUTH_TYPE); + if (auth_types == NULL) { + /* No allowed auth types are specified, so set the defaults. */ + allowed_auth_types = default_auth_types; + goto done; + } + + /* Check each type to see if it is set. */ + if (ipapwd_is_auth_type_allowed(auth_types, IPA_OTP_AUTH_TYPE_DISABLED)) { + allowed_auth_types |= IPA_OTP_AUTH_TYPE_DISABLED; + } + + if (ipapwd_is_auth_type_allowed(auth_types, IPA_OTP_AUTH_TYPE_PASSWORD)) { + allowed_auth_types |= IPA_OTP_AUTH_TYPE_PASSWORD; + } + + if (ipapwd_is_auth_type_allowed(auth_types, IPA_OTP_AUTH_TYPE_OTP)) { + allowed_auth_types |= IPA_OTP_AUTH_TYPE_OTP; + } + + if (ipapwd_is_auth_type_allowed(auth_types, IPA_OTP_AUTH_TYPE_PKINIT)) { + allowed_auth_types |= IPA_OTP_AUTH_TYPE_PKINIT; + } + + if (ipapwd_is_auth_type_allowed(auth_types, IPA_OTP_AUTH_TYPE_RADIUS)) { + allowed_auth_types |= IPA_OTP_AUTH_TYPE_RADIUS; + } + + slapi_ch_array_free(auth_types); + +done: + if (apply) { + /* Atomically set the global allowed types. */ + PR_ATOMIC_SET(&g_allowed_auth_types, allowed_auth_types); + } + + return true; +} + +bool ipapwd_otp_is_disabled(void) +{ + PRInt32 auth_type_flags; + + /* Do an atomic read of the allowed auth types bit field. */ + auth_type_flags = PR_ATOMIC_ADD(&g_allowed_auth_types, 0); + + /* Check if the disabled bit is set. */ + return auth_type_flags & IPA_OTP_AUTH_TYPE_DISABLED; +} + static struct ipapwd_krbcfg *ipapwd_getConfig(void) { krb5_error_code krberr; diff --git a/daemons/ipa-slapi-plugins/ipa-pwd-extop/ipa_pwd_extop.c b/daemons/ipa-slapi-plugins/ipa-pwd-extop/ipa_pwd_extop.c index b64084e9d..88cdcb10f 100644 --- a/daemons/ipa-slapi-plugins/ipa-pwd-extop/ipa_pwd_extop.c +++ b/daemons/ipa-slapi-plugins/ipa-pwd-extop/ipa_pwd_extop.c @@ -87,6 +87,30 @@ Slapi_PluginDesc ipapwd_plugin_desc = { void *ipapwd_plugin_id; static int usetxn = 0; +static Slapi_DN *_ConfigAreaDN = NULL; +static Slapi_DN *_PluginDN = NULL; +static bool g_plugin_started = false; + +void *ipapwd_get_plugin_id(void) +{ + return ipapwd_plugin_id; +} + +Slapi_DN *ipapwd_get_otp_config_area(void) +{ + return _ConfigAreaDN; +} + +Slapi_DN *ipapwd_get_plugin_sdn(void) +{ + return _PluginDN; +} + +bool ipapwd_get_plugin_started(void) +{ + return g_plugin_started; +} + static int filter_keys(struct ipapwd_krbcfg *krbcfg, struct ipapwd_keyset *kset) { @@ -1195,6 +1219,35 @@ Slapi_Filter *ipapwd_string2filter(char *strfilter) return ret; } +/* Loads the OTP config entry, parses it, and applies it. */ +static inline bool ipapwd_load_otp_config(void) +{ + char *config_attrs[] = { IPA_USER_AUTH_TYPE, NULL }; + Slapi_Entry *config_entry = NULL; + Slapi_DN *config_sdn = NULL; + + /* If we are using an alternate config area, check it for our + * configuration, otherwise we just use our main plug-in config + * entry. */ + if ((config_sdn = ipapwd_get_otp_config_area()) == NULL) { + config_sdn = ipapwd_get_plugin_sdn(); + } + + slapi_log_error(SLAPI_LOG_PLUGIN, IPAPWD_PLUGIN_NAME, + "Looking for config settings in \"%s\".\n", + config_sdn ? slapi_sdn_get_ndn(config_sdn) : "null"); + + /* Fetch the config entry. */ + slapi_search_internal_get_entry(config_sdn, config_attrs, &config_entry, + ipapwd_plugin_id); + + /* Parse and apply the config. */ + ipapwd_parse_otp_config_entry(config_entry, true); + + slapi_entry_free(config_entry); + return true; +} + /* Init data structs */ static int ipapwd_start( Slapi_PBlock *pb ) { @@ -1203,8 +1256,37 @@ static int ipapwd_start( Slapi_PBlock *pb ) char *realm = NULL; char *config_dn; Slapi_Entry *config_entry = NULL; + Slapi_DN *plugindn = NULL; + char *config_area = NULL; int ret; + /* Check if we're already started */ + if (g_plugin_started) { + return LDAP_SUCCESS; + } + + /* Get the plug-in target dn from the system and store for future use. */ + slapi_pblock_get(pb, SLAPI_TARGET_SDN, &plugindn); + if (plugindn == NULL || slapi_sdn_get_ndn_len(plugindn) == 0) { + LOG_FATAL("No plugin dn?\n"); + return LDAP_OPERATIONS_ERROR; + } + _PluginDN = slapi_sdn_dup(plugindn); + + /* Set the alternate config area if one is defined. */ + slapi_pblock_get(pb, SLAPI_PLUGIN_CONFIG_AREA, &config_area); + if (config_area != NULL) { + _ConfigAreaDN = slapi_sdn_new_normdn_byval(config_area); + } + + /* + * Load the config. + */ + if (!ipapwd_load_otp_config()) { + LOG_FATAL("Unable to load plug-in config\n"); + return LDAP_OPERATIONS_ERROR; + } + krberr = krb5_init_context(&krbctx); if (krberr) { LOG_FATAL("krb5_init_context failed\n"); @@ -1273,6 +1355,7 @@ static int ipapwd_start( Slapi_PBlock *pb ) } ret = LDAP_SUCCESS; + g_plugin_started = true; done: free(realm); @@ -1281,7 +1364,25 @@ done: return ret; } +/* Clean up any resources allocated at startup. */ +static int ipapwd_close(Slapi_PBlock * pb) +{ + if (!g_plugin_started) { + goto done; + } + g_plugin_started = false; + + /* We are not guaranteed that other threads are finished accessing + * PluginDN or ConfigAreaDN, so we don't want to free them. This is + * only a one-time leak at shutdown, so it should be fine. + * slapi_sdn_free(&_PluginDN); + * slapi_sdn_free(&_ConfigAreaDN); + */ + +done: + return 0; +} static char *ipapwd_oid_list[] = { EXOP_PASSWD_OID, @@ -1328,12 +1429,13 @@ int ipapwd_init( Slapi_PBlock *pb ) * plug-in function that handles the operation identified by * OID 1.3.6.1.4.1.4203.1.11.1 . Also specify the version of the server * plug-in */ - ret = slapi_pblock_set(pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_01); + ret = slapi_pblock_set(pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_03); if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_START_FN, (void *)ipapwd_start); if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_DESCRIPTION, (void *)&ipapwd_plugin_desc); if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_EXT_OP_OIDLIST, ipapwd_oid_list); if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_EXT_OP_NAMELIST, ipapwd_name_list); if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_EXT_OP_FN, (void *)ipapwd_extop); + if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_CLOSE_FN, (void *)ipapwd_close); if (ret) { LOG("Failed to set plug-in version, function, and OID.\n" ); return -1; @@ -1361,5 +1463,10 @@ int ipapwd_init( Slapi_PBlock *pb ) "IPA pwd post ops", NULL, ipapwd_plugin_id); + slapi_register_plugin("internalpostoperation", 1, + "ipapwd_intpost_init", ipapwd_intpost_init, + "IPA pwd internal post ops", NULL, + ipapwd_plugin_id); + return 0; } diff --git a/daemons/ipa-slapi-plugins/ipa-pwd-extop/ipapwd.h b/daemons/ipa-slapi-plugins/ipa-pwd-extop/ipapwd.h index 372441ddd..74b636276 100644 --- a/daemons/ipa-slapi-plugins/ipa-pwd-extop/ipapwd.h +++ b/daemons/ipa-slapi-plugins/ipa-pwd-extop/ipapwd.h @@ -76,6 +76,30 @@ #define IPA_CHANGETYPE_ADMIN 1 #define IPA_CHANGETYPE_DSMGR 2 +/* + * Attribute type defines + */ +#define IPA_USER_AUTH_TYPE "ipaUserAuthType" +#define IPA_OTP_TOKEN_OWNER_TYPE "ipaTokenOwner" +#define IPA_OTP_TOKEN_LENGTH_TYPE "ipaTokenOTPDigits" +#define IPA_OTP_TOKEN_KEY_TYPE "ipaTokenOTPKey" +#define IPA_OTP_TOKEN_ALGORITHM_TYPE "ipaTokenOTPAlgorithm" +#define IPA_OTP_TOKEN_OFFSET_TYPE "ipaTokenTOTPClockOffset" +#define IPA_OTP_TOKEN_STEP_TYPE "ipaTokenTOTPTimeStep" + +/* Authentication type defines */ +#define IPA_OTP_AUTH_TYPE_NONE 0 +#define IPA_OTP_AUTH_TYPE_DISABLED 1 +#define IPA_OTP_AUTH_TYPE_PASSWORD 2 +#define IPA_OTP_AUTH_TYPE_OTP 4 +#define IPA_OTP_AUTH_TYPE_PKINIT 8 +#define IPA_OTP_AUTH_TYPE_RADIUS 16 +#define IPA_OTP_AUTH_TYPE_VALUE_DISABLED "DISABLED" +#define IPA_OTP_AUTH_TYPE_VALUE_PASSWORD "PASSWORD" +#define IPA_OTP_AUTH_TYPE_VALUE_OTP "OTP" +#define IPA_OTP_AUTH_TYPE_VALUE_PKINIT "PKINIT" +#define IPA_OTP_AUTH_TYPE_VALUE_RADIUS "RADIUS" + struct ipapwd_data { Slapi_Entry *target; char *dn; @@ -112,6 +136,9 @@ struct ipapwd_krbcfg { bool allow_nt_hash; }; +bool ipapwd_is_auth_type_allowed(char **auth_type_list, int auth_type); +bool ipapwd_parse_otp_config_entry(Slapi_Entry * e, bool apply); +bool ipapwd_otp_is_disabled(void); int ipapwd_entry_checks(Slapi_PBlock *pb, struct slapi_entry *e, int *is_root, int *is_krb, int *is_smb, int *is_ipant, char *attr, int access); @@ -152,6 +179,15 @@ int ipapwd_gen_hashes(struct ipapwd_krbcfg *krbcfg, int ipapwd_ext_init(void); int ipapwd_pre_init(Slapi_PBlock *pb); int ipapwd_post_init(Slapi_PBlock *pb); +int ipapwd_intpost_init(Slapi_PBlock *pb); int ipapwd_pre_init_betxn(Slapi_PBlock *pb); int ipapwd_post_init_betxn(Slapi_PBlock *pb); +/* from ipa_pwd_extop.c */ +void *ipapwd_get_plugin_id(void); +Slapi_DN *ipapwd_get_otp_config_area(void); +Slapi_DN *ipapwd_get_plugin_sdn(void); +bool ipapwd_get_plugin_started(void); + +/* from auth.c */ +bool ipapwd_do_otp_auth(Slapi_Entry *bind_entry, struct berval *creds); diff --git a/daemons/ipa-slapi-plugins/ipa-pwd-extop/otp.c b/daemons/ipa-slapi-plugins/ipa-pwd-extop/otp.c new file mode 100644 index 000000000..6c0f8554b --- /dev/null +++ b/daemons/ipa-slapi-plugins/ipa-pwd-extop/otp.c @@ -0,0 +1,180 @@ +/** 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 +#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 ipapwd_hotp(const uint8_t *key, size_t len, const char *algo, int digits, + uint64_t counter, uint32_t *out) +{ + const SECItem cntr = { siBuffer, (uint8_t *) &counter, sizeof(counter) }; + SECItem keyitm = { siBuffer, (uint8_t *) key, len }; + CK_MECHANISM_TYPE mech = CKM_SHA_1_HMAC; + PRUint64 offset, binary, div; + struct digest_buffer digest; + 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, 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; +} + +/* + * An implementation of TOTP (RFC 6238). + */ +bool ipapwd_totp(const uint8_t *key, size_t len, const char *algo, int digits, + time_t time, int offset, unsigned int step, uint32_t *out) +{ + if (step == 0) + return false; + + return ipapwd_hotp(key, len, algo, digits, (time - offset) / step, out); +} diff --git a/daemons/ipa-slapi-plugins/ipa-pwd-extop/prepost.c b/daemons/ipa-slapi-plugins/ipa-pwd-extop/prepost.c index 0318cecdc..8a222650c 100644 --- a/daemons/ipa-slapi-plugins/ipa-pwd-extop/prepost.c +++ b/daemons/ipa-slapi-plugins/ipa-pwd-extop/prepost.c @@ -67,6 +67,9 @@ #define IPAPWD_OP_ADD 1 #define IPAPWD_OP_MOD 2 +#define IPAPWD_OP_NOT_HANDLED 0 +#define IPAPWD_OP_HANDLED 1 + extern Slapi_PluginDesc ipapwd_plugin_desc; extern void *ipapwd_plugin_id; extern const char *ipa_realm_tree; @@ -975,7 +978,77 @@ static int ipapwd_regen_nthash(Slapi_PBlock *pb, Slapi_Mods *smods, return ret; } -static int ipapwd_post_op(Slapi_PBlock *pb) +/* + * Check if we want to process this operation. We need to be + * sure that the operation succeeded. + */ +static bool ipapwd_otp_oktodo(Slapi_PBlock *pb) +{ + bool ok = false; + int oprc = 0; + int ret = 1; + + ret = slapi_pblock_get(pb, SLAPI_PLUGIN_OPRETURN, &oprc); + if (ret != 0) { + LOG_FATAL("Could not get parameters\n"); + goto done; + } + + /* This plugin should only execute if the operation succeeded. */ + ok = oprc == 0; + +done: + return ok; +} + +static bool ipapwd_dn_is_otp_config(Slapi_DN *sdn) +{ + bool ret = false; + Slapi_DN *dn; + + /* If an alternate config area is configured, it is considered to be + * the config entry, otherwise the main plug-in config entry is used. */ + if (sdn != NULL) { + dn = ipapwd_get_otp_config_area(); + if (dn == NULL) + dn = ipapwd_get_plugin_sdn(); + + ret = slapi_sdn_compare(sdn, dn) == 0; + } + + return ret; +} + +static int ipapwd_post_modadd_otp(Slapi_PBlock *pb) +{ + Slapi_Entry *config_entry = NULL; + Slapi_DN *sdn = NULL; + + /* Just bail if we are not started yet, or if the operation failed. */ + if (!ipapwd_get_plugin_started() || !ipapwd_otp_oktodo(pb)) { + goto done; + } + + /* Check if a change affected our config entry and reload the + * in-memory config settings if needed. */ + slapi_pblock_get(pb, SLAPI_TARGET_SDN, &sdn); + if (ipapwd_dn_is_otp_config(sdn)) { + /* The config entry was added or modified, so reload it from + * the post-op entry. */ + slapi_pblock_get(pb, SLAPI_ENTRY_POST_OP, &config_entry); + if (config_entry == NULL) { + LOG_FATAL("Unable to retrieve config entry.\n"); + goto done; + } + + ipapwd_parse_otp_config_entry(config_entry, true); + } + +done: + return 0; +} + +static int ipapwd_post_modadd(Slapi_PBlock *pb) { void *op; struct ipapwd_operation *pwdop = NULL; @@ -991,6 +1064,11 @@ static int ipapwd_post_op(Slapi_PBlock *pb) LOG_TRACE("=>\n"); + ret = ipapwd_post_modadd_otp(pb); + if (ret != 0) { + return ret; + } + /* time to get the operation handler */ ret = slapi_pblock_get(pb, SLAPI_OPERATION, &op); if (ret != 0) { @@ -1111,6 +1189,202 @@ done: return 0; } +static int ipapwd_post_modrdn_otp(Slapi_PBlock *pb) +{ + Slapi_Entry *config_entry = NULL; + Slapi_DN *new_sdn = NULL; + Slapi_DN *sdn = NULL; + + /* Just bail if we are not started yet, or if the operation failed. */ + if (!ipapwd_get_plugin_started() || !ipapwd_otp_oktodo(pb)) { + goto done; + } + + /* Check if a change affected our config entry and reload the + * in-memory config settings if needed. */ + slapi_pblock_get(pb, SLAPI_TARGET_SDN, &sdn); + if (ipapwd_dn_is_otp_config(sdn)) { + /* Our config entry was renamed. We treat this like the entry + * was deleted, so just set the defaults. */ + ipapwd_parse_otp_config_entry(NULL, true); + } else { + /* Check if an entry was renamed such that it has become our + * config entry. If so, reload the config from this new entry. */ + slapi_pblock_get(pb, SLAPI_ENTRY_POST_OP, &config_entry); + if (config_entry == NULL) { + LOG_FATAL("Unable to retrieve renamed entry.\n"); + goto done; + } + + new_sdn = slapi_entry_get_sdn(config_entry); + if (new_sdn == NULL) { + LOG_FATAL("Unable to retrieve DN of renamed entry.\n"); + goto done; + } + + if (ipapwd_dn_is_otp_config(new_sdn)) { + ipapwd_parse_otp_config_entry(config_entry, true); + } + } + +done: + return 0; +} + +static int ipapwd_post_del_otp(Slapi_PBlock *pb) +{ + Slapi_DN *sdn = NULL; + int ret = 0; + + /* Just bail if we are not started yet, or if the operation failed. */ + if (!ipapwd_get_plugin_started() || !ipapwd_otp_oktodo(pb)) { + goto done; + } + + /* Check if a change affected our config entry and reload the + * in-memory config settings if needed. */ + slapi_pblock_get(pb, SLAPI_TARGET_SDN, &sdn); + if (ipapwd_dn_is_otp_config(sdn)) { + /* The config entry was deleted, so this just sets the defaults. */ + ipapwd_parse_otp_config_entry(NULL, true); + } + +done: + return ret; +} + +/* Handle OTP authentication. */ +static int ipapwd_pre_bind_otp(Slapi_PBlock * pb) +{ + char *user_attrs[] = { IPA_USER_AUTH_TYPE, NULL }; + int ret = IPAPWD_OP_NOT_HANDLED; + Slapi_Entry *bind_entry = NULL; + struct berval *creds = NULL; + const char *bind_dn = NULL; + Slapi_DN *bind_sdn = NULL; + int result = LDAP_SUCCESS; + char **auth_types = NULL; + int method; + int i; + + /* If we didn't start successfully, bail. */ + if (!ipapwd_get_plugin_started()) { + goto done; + } + + /* If global disabled flag is set, just punt. */ + if (ipapwd_otp_is_disabled()) { + goto done; + } + + /* Retrieve parameters for bind operation. */ + i = slapi_pblock_get(pb, SLAPI_BIND_METHOD, &method); + if (i == 0) { + i = slapi_pblock_get(pb, SLAPI_BIND_TARGET_SDN, &bind_sdn); + if (i == 0) { + i = slapi_pblock_get(pb, SLAPI_BIND_CREDENTIALS, &creds); + } + } + if (i != 0) { + LOG_FATAL("Not handled (can't retrieve bind parameters)\n"); + goto done; + } + + bind_dn = slapi_sdn_get_dn(bind_sdn); + + /* We only handle non-anonymous simple binds. We just pass everything + * else through to the server. */ + if (method != LDAP_AUTH_SIMPLE || *bind_dn == '\0' || creds->bv_len == 0) { + LOG_TRACE("Not handled (not simple bind or NULL dn/credentials)\n"); + goto done; + } + + /* Check if any allowed authentication types are set in the user entry. + * If not, we just use the global settings from the config entry. */ + result = slapi_search_internal_get_entry(bind_sdn, user_attrs, &bind_entry, + ipapwd_get_plugin_id()); + if (result != LDAP_SUCCESS) { + LOG_FATAL("Not handled (could not search for BIND dn %s - error " + "%d : %s)\n", bind_dn, result, ldap_err2string(result)); + goto done; + } + if (bind_entry == NULL) { + LOG_FATAL("Not handled (could not find entry for BIND dn %s)\n", bind_dn); + goto done; + } + + i = slapi_check_account_lock(pb, bind_entry, 0, 0, 0); + if (i == 1) { + LOG_TRACE("Not handled (account %s inactivated.)\n", bind_dn); + goto done; + } + + auth_types = slapi_entry_attr_get_charray(bind_entry, IPA_USER_AUTH_TYPE); + + /* + * IMPORTANT SECTION! + * + * This section handles authentication logic, so be careful! + * + * The basic idea of this section is: + * 1. If OTP is enabled, try to use it first. If successful, send response. + * 2. If OTP was not enabled/successful, check if password is enabled. + * 3. If password is not enabled, send failure response. + * 4. Otherwise, fall through to standard server password authentication. + * + */ + + /* If OTP is allowed, attempt to do OTP authentication. */ + if (ipapwd_is_auth_type_allowed(auth_types, IPA_OTP_AUTH_TYPE_OTP)) { + LOG_PLUGIN_NAME(IPAPWD_PLUGIN_NAME, + "Attempting OTP authentication for '%s'.\n", bind_dn); + if (ipapwd_do_otp_auth(bind_entry, creds)) { + /* FIXME - NGK - If the auth type request control was sent, + * construct the response control to indicate what auth type was + * used. We might be able to do this in the + * SLAPI_PLUGIN_PRE_RESULT_FN callback instead of here. */ + + /* FIXME - NGK - What about other controls, like the pwpolicy + * control? If any other critical controls are set, we need to + * either process them properly or reject the operation with an + * unsupported critical control error. */ + + /* Send response approving authentication. */ + slapi_send_ldap_result(pb, LDAP_SUCCESS, NULL, NULL, 0, NULL); + ret = IPAPWD_OP_HANDLED; + } + } + + /* If OTP failed or was not enabled, we need to figure out if we can fall + * back to standard password authentication or give an error. */ + if (ret != IPAPWD_OP_HANDLED) { + if (!ipapwd_is_auth_type_allowed(auth_types, + IPA_OTP_AUTH_TYPE_PASSWORD)) { + /* Password authentication is disabled, so we have failed. */ + slapi_send_ldap_result(pb, LDAP_INVALID_CREDENTIALS, + NULL, NULL, 0, NULL); + ret = IPAPWD_OP_HANDLED; + goto done; + } + + /* Password authentication is permitted, so tell the server that we + * didn't handle this request. Then the server will perform standard + * password authentication. */ + LOG_PLUGIN_NAME(IPAPWD_PLUGIN_NAME, + "Attempting PASSWORD authentication for \"%s\".\n", + bind_dn); + + /* FIXME - NGK - Do we need to figure out how to build + * the reponse control in this case? Maybe we can use a + * SLAPI_PLUGIN_PRE_RESULT_FN callback to handle that? */ + } + +done: + slapi_ch_array_free(auth_types); + slapi_entry_free(bind_entry); + return ret; +} + /* PRE BIND Operation: * Used for password migration from DS to IPA. * Gets the clean text password, authenticates the user and generates @@ -1137,6 +1411,12 @@ static int ipapwd_pre_bind(Slapi_PBlock *pb) LOG_TRACE("=>\n"); + /* Try to do OTP first. */ + ret = ipapwd_pre_bind_otp(pb); + if (ret == IPAPWD_OP_HANDLED) { + return ret; + } + /* get BIND parameters */ ret |= slapi_pblock_get(pb, SLAPI_BIND_TARGET, &dn); ret |= slapi_pblock_get(pb, SLAPI_BIND_METHOD, &method); @@ -1295,8 +1575,6 @@ done: return 0; } - - /* Init pre ops */ int ipapwd_pre_init(Slapi_PBlock *pb) { @@ -1330,20 +1608,35 @@ int ipapwd_post_init(Slapi_PBlock *pb) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_01); if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_DESCRIPTION, (void *)&ipapwd_plugin_desc); - if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_POST_ADD_FN, (void *)ipapwd_post_op); - if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_POST_MODIFY_FN, (void *)ipapwd_post_op); + if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_POST_ADD_FN, (void *)ipapwd_post_modadd); + if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_POST_DELETE_FN, (void *)ipapwd_post_del_otp); + if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_POST_MODIFY_FN, (void *)ipapwd_post_modadd); + if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_POST_MODRDN_FN, (void *)ipapwd_post_modrdn_otp); return ret; } +int ipapwd_intpost_init(Slapi_PBlock *pb) +{ + int ret; + + ret = slapi_pblock_set(pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_03); + if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_DESCRIPTION, (void *)&ipapwd_plugin_desc); + if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_INTERNAL_POST_ADD_FN, (void *)ipapwd_post_modadd_otp); + if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_INTERNAL_POST_DELETE_FN, (void *)ipapwd_post_del_otp); + if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_INTERNAL_POST_MODIFY_FN, (void *)ipapwd_post_modadd_otp); + if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_INTERNAL_POST_MODRDN_FN, (void *)ipapwd_post_modrdn_otp); + return ret; +} + int ipapwd_post_init_betxn(Slapi_PBlock *pb) { int ret; ret = slapi_pblock_set(pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_01); if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_DESCRIPTION, (void *)&ipapwd_plugin_desc); - if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_BE_TXN_POST_ADD_FN, (void *)ipapwd_post_op); - if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_BE_TXN_POST_MODIFY_FN, (void *)ipapwd_post_op); + if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_BE_TXN_POST_ADD_FN, (void *)ipapwd_post_modadd); + if (!ret) ret = slapi_pblock_set(pb, SLAPI_PLUGIN_BE_TXN_POST_MODIFY_FN, (void *)ipapwd_post_modadd); return ret; } diff --git a/daemons/ipa-slapi-plugins/ipa-pwd-extop/t_hotp.c b/daemons/ipa-slapi-plugins/ipa-pwd-extop/t_hotp.c new file mode 100644 index 000000000..d57f9ab68 --- /dev/null +++ b/daemons/ipa-slapi-plugins/ipa-pwd-extop/t_hotp.c @@ -0,0 +1,82 @@ +/** 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 +#include +#include +#include +#include +#include +#include + +/* + * From otp.c + */ +bool ipapwd_hotp(const uint8_t *key, size_t len, const char *algo, int digits, + uint64_t counter, uint32_t *out); + +/* All HOTP test examples from RFC 4226 (Appendix D). */ +static const uint8_t *key = (uint8_t *) "12345678901234567890"; +static const uint32_t answers[] = { + 755224, + 287082, + 359152, + 969429, + 338314, + 254676, + 287922, + 162583, + 399871, + 520489 +}; + +int +main(int argc, const char *argv[]) +{ + uint32_t otp; + int i; + + NSS_NoDB_Init("."); + + for (i = 0; i < sizeof(answers) / sizeof(*answers); i++) { + assert(ipapwd_hotp(key, 20, "sha1", 6, i, &otp)); + assert(otp == answers[i]); + } + + NSS_Shutdown(); + return 0; +} diff --git a/daemons/ipa-slapi-plugins/ipa-pwd-extop/t_totp.c b/daemons/ipa-slapi-plugins/ipa-pwd-extop/t_totp.c new file mode 100644 index 000000000..2df8d2458 --- /dev/null +++ b/daemons/ipa-slapi-plugins/ipa-pwd-extop/t_totp.c @@ -0,0 +1,103 @@ +/** 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 +#include +#include +#include +#include +#include +#include + +/* + * From otp.c + */ +bool ipapwd_totp(const uint8_t *key, size_t len, const char *algo, int digits, + time_t time, int offset, unsigned int step, uint32_t *out); + +#define SHA1 "sha1", (uint8_t *) "12345678901234567890", 20 +#define SHA256 "sha256", (uint8_t *) "12345678901234567890123456789012", 32 +#define SHA512 "sha512", (uint8_t *) "12345678901234567890123456789012" \ + "34567890123456789012345678901234", 64 + +/* All TOTP test examples from RFC 6238 (Appendix B). */ +const static struct { + const char *algo; + const uint8_t *key; + size_t len; + time_t time; + uint32_t answer; +} 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(tests) / sizeof(*tests); i++) { + assert(ipapwd_totp(tests[i].key, tests[i].len, tests[i].algo, + 8, tests[i].time, 0, 30, &otp)); + assert(otp == tests[i].answer); + } + + NSS_Shutdown(); + return 0; +} -- cgit