summaryrefslogtreecommitdiffstats
path: root/daemons/ipa-slapi-plugins/ipa-pwd-extop/auth.c
diff options
context:
space:
mode:
Diffstat (limited to 'daemons/ipa-slapi-plugins/ipa-pwd-extop/auth.c')
-rw-r--r--daemons/ipa-slapi-plugins/ipa-pwd-extop/auth.c398
1 files changed, 398 insertions, 0 deletions
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;
+}