[SSSD] [PATCHES] Add special PAM prompting for 2FA authentication
Sumit Bose
sbose at redhat.com
Wed Apr 22 08:24:25 UTC 2015
On Mon, Apr 06, 2015 at 08:58:16PM +0200, Sumit Bose wrote:
> Hi,
>
> this series of patches tries to add special prompting in the PAM dialog
> for 2-Factor-Authentication (2FA) and use it where suitable. As
> discussed on
> https://fedorahosted.org/sssd/wiki/DesignDocs/PAMConversationForOTP if
> long-term password and one-time component are entered in a single prompt
> SSSD is in general not able to split them and use the long-term password
> of offline authentication or some user keyrings. With this patch-set an
> IPA user with configured OTP token will be prompted to enter the two
> factors separately (if the login application supports it) and the first
> factor (long-term password) will be save into the cache for offline
> authentication if it is long enough.
>
> Two new internals are needed for this. A special authentication token
> type for 2FA and a PAM pre-auth request to check if the given user user
> a standard password or 2FA/OTP.
>
> The first two patches enhance the authtok module to handle 2FA.
> Patches 4-6 add the pre-auth request to PAM client, responder and IPA
> backend. Patches 7-12 add the prompting, the minimal length check and
> the caching of the first factor. Finally tests for the PAM responder are
> added which should test existing and new behaviour.
>
Please find attached a new version of the patch set which contains a few
fixes which should silence a couple of compiler warnings.
bye,
Sumit
-------------- next part --------------
From df63d2b3012242a847254081a232c61dbd7e2b8f Mon Sep 17 00:00:00 2001
From: Sumit Bose <sbose at redhat.com>
Date: Thu, 12 Feb 2015 21:53:15 +0100
Subject: [PATCH 01/14] Add leak check and command line option to test_authtok
---
Makefile.am | 3 ++
src/tests/cmocka/test_authtok.c | 67 +++++++++++++++++++++++++++++++++++------
2 files changed, 60 insertions(+), 10 deletions(-)
diff --git a/Makefile.am b/Makefile.am
index 52fbd510d67489dfd65b003c99673dc0869cfdc0..233d171b89ee2003881c58fb74fad808702746ae 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -1858,11 +1858,14 @@ test_authtok_SOURCES = \
test_authtok_CFLAGS = \
$(AM_CFLAGS) \
$(TALLOC_CFLAGS) \
+ $(POPT_CFLAGS) \
$(DHASH_CFLAGS)
test_authtok_LDADD = \
$(TALLOC_LIBS) \
$(CMOCKA_LIBS) \
$(DHASH_LIBS) \
+ $(POPT_LIBS) \
+ libsss_test_common.la \
libsss_debug.la
sss_nss_idmap_tests_SOURCES = \
diff --git a/src/tests/cmocka/test_authtok.c b/src/tests/cmocka/test_authtok.c
index e37e92f68373d564f53b1267f078ea89c31ae051..0c7b7197fb2c03d69dc4df2310229ea100ad28d4 100644
--- a/src/tests/cmocka/test_authtok.c
+++ b/src/tests/cmocka/test_authtok.c
@@ -22,11 +22,10 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
-#include <stdarg.h>
-#include <stddef.h>
-#include <setjmp.h>
-#include <cmocka.h>
#include <string.h>
+#include <popt.h>
+
+#include "tests/cmocka/common_mock.h"
#include "util/authtok.h"
@@ -39,12 +38,15 @@ static int setup(void **state)
{
struct test_state *ts = NULL;
- ts = talloc(NULL, struct test_state);
+ assert_true(leak_check_setup());
+
+ ts = talloc(global_talloc_context, struct test_state);
assert_non_null(ts);
ts->authtoken = sss_authtok_new(ts);
assert_non_null(ts->authtoken);
+ check_leaks_push(ts);
*state = (void *)ts;
return 0;
}
@@ -52,7 +54,12 @@ static int setup(void **state)
static int teardown(void **state)
{
struct test_state *ts = talloc_get_type_abort(*state, struct test_state);
+
+ assert_non_null(ts);
+
+ assert_true(check_leaks_pop(ts) == true);
talloc_free(ts);
+ assert_true(leak_check_teardown());
return 0;
}
@@ -85,7 +92,7 @@ static void test_sss_authtok_password(void **state)
{
size_t len;
errno_t ret;
- const char *data;
+ char *data;
size_t ret_len;
const char *pwd;
struct test_state *ts;
@@ -117,6 +124,9 @@ static void test_sss_authtok_password(void **state)
assert_int_equal(ret, EOK);
assert_string_equal(data, pwd);
assert_int_equal(len - 1, ret_len);
+
+ talloc_free(data);
+ sss_authtok_set_empty(ts->authtoken);
}
/* Test when type has value SSS_AUTHTOK_TYPE_CCFILE */
@@ -124,7 +134,7 @@ static void test_sss_authtok_ccfile(void **state)
{
size_t len;
errno_t ret;
- const char *data;
+ char *data;
size_t ret_len;
const char *pwd;
struct test_state *ts;
@@ -172,6 +182,9 @@ static void test_sss_authtok_ccfile(void **state)
assert_int_equal(ret, EOK);
assert_string_equal(data, pwd);
assert_int_equal(len - 1, ret_len);
+
+ talloc_free(data);
+ sss_authtok_set_empty(ts->authtoken);
}
/* Test when type has value SSS_AUTHTOK_TYPE_EMPTY */
@@ -226,7 +239,7 @@ static void test_sss_authtok_wipe_password(void **state)
{
size_t len;
errno_t ret;
- const char *data;
+ char *data;
size_t ret_len;
const char *pwd;
struct test_state *ts;
@@ -249,13 +262,16 @@ static void test_sss_authtok_wipe_password(void **state)
assert_int_equal(ret, EOK);
assert_string_equal(pwd, "");
assert_int_equal(len - 1, ret_len);
+
+ sss_authtok_set_empty(ts->authtoken);
+ talloc_free(data);
}
static void test_sss_authtok_copy(void **state)
{
size_t len;
errno_t ret;
- const char *data;
+ char *data;
struct test_state *ts;
enum sss_authtok_type type;
struct sss_auth_token *dest_authtoken;
@@ -276,6 +292,7 @@ static void test_sss_authtok_copy(void **state)
assert_int_equal(EOK, sss_authtok_copy(ts->authtoken, dest_authtoken));
assert_int_equal(type, sss_authtok_get_type(dest_authtoken));
+ sss_authtok_set_empty(dest_authtoken);
type = SSS_AUTHTOK_TYPE_PASSWORD;
ret = sss_authtok_set(ts->authtoken, type, (const uint8_t *)data, len);
@@ -287,10 +304,23 @@ static void test_sss_authtok_copy(void **state)
assert_int_equal(type, sss_authtok_get_type(dest_authtoken));
assert_string_equal(data, sss_authtok_get_data(dest_authtoken));
assert_int_equal(len, sss_authtok_get_size(dest_authtoken));
+
+ sss_authtok_set_empty(dest_authtoken);
+ talloc_free(dest_authtoken);
+ sss_authtok_set_empty(ts->authtoken);
+ talloc_free(data);
}
-int main(void)
+int main(int argc, const char *argv[])
{
+ poptContext pc;
+ int opt;
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+ SSSD_DEBUG_OPTS
+ POPT_TABLEEND
+ };
+
const struct CMUnitTest tests[] = {
cmocka_unit_test_setup_teardown(test_sss_authtok_new,
setup, teardown),
@@ -306,5 +336,22 @@ int main(void)
setup, teardown)
};
+ /* Set debug level to invalid value so we can deside if -d 0 was used. */
+ debug_level = SSSDBG_INVALID;
+
+ pc = poptGetContext(argv[0], argc, argv, long_options, 0);
+ while((opt = poptGetNextOpt(pc)) != -1) {
+ switch(opt) {
+ default:
+ fprintf(stderr, "\nInvalid option %s: %s\n\n",
+ poptBadOption(pc, 0), poptStrerror(opt));
+ poptPrintUsage(pc, stderr, 0);
+ return 1;
+ }
+ }
+ poptFreeContext(pc);
+
+ DEBUG_CLI_INIT(debug_level);
+
return cmocka_run_group_tests(tests, NULL, NULL);
}
--
2.1.0
-------------- next part --------------
From 3fe2e9fe20e41e4c8ed31ab94655d68debeb3268 Mon Sep 17 00:00:00 2001
From: Sumit Bose <sbose at redhat.com>
Date: Wed, 7 Jan 2015 18:11:16 +0100
Subject: [PATCH 02/14] utils: add sss_authtok_[gs]et_2fa
---
Makefile.am | 5 ++
src/sss_client/pam_sss.c | 1 +
src/sss_client/sss_cli.h | 3 +
src/tests/cmocka/test_authtok.c | 154 +++++++++++++++++++++++++++++++++-
src/util/authtok-utils.c | 74 ++++++++++++++++
src/util/authtok-utils.h | 55 ++++++++++++
src/util/authtok.c | 182 ++++++++++++++++++++++++++++++++++++++++
src/util/authtok.h | 44 ++++++++++
8 files changed, 517 insertions(+), 1 deletion(-)
create mode 100644 src/util/authtok-utils.c
create mode 100644 src/util/authtok-utils.h
diff --git a/Makefile.am b/Makefile.am
index 233d171b89ee2003881c58fb74fad808702746ae..5f400585141f8ebf11b51171ffc0f60070261b42 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -519,6 +519,7 @@ dist_noinst_HEADERS = \
src/util/atomic_io.h \
src/util/auth_utils.h \
src/util/authtok.h \
+ src/util/authtok-utils.h \
src/util/util_safealign.h \
src/util/util_sss_idmap.h \
src/monitor/monitor.h \
@@ -754,6 +755,7 @@ libsss_util_la_SOURCES = \
src/util/murmurhash3.c \
src/util/atomic_io.c \
src/util/authtok.c \
+ src/util/authtok-utils.c \
src/util/sss_selinux.c \
src/util/domain_info_utils.c \
src/util/util_lock.c \
@@ -1854,6 +1856,7 @@ test_negcache_LDADD = \
test_authtok_SOURCES = \
src/tests/cmocka/test_authtok.c \
src/util/authtok.c \
+ src/util/authtok-utils.c \
src/util/util.c
test_authtok_CFLAGS = \
$(AM_CFLAGS) \
@@ -2780,6 +2783,7 @@ krb5_child_SOURCES = \
src/util/find_uid.c \
src/util/atomic_io.c \
src/util/authtok.c \
+ src/util/authtok-utils.c \
src/util/util.c \
src/util/signal.c \
src/util/strtonum.c \
@@ -2811,6 +2815,7 @@ ldap_child_SOURCES = \
src/util/sss_krb5.c \
src/util/atomic_io.c \
src/util/authtok.c \
+ src/util/authtok-utils.c \
src/util/util.c \
src/util/signal.c \
src/util/become_user.c \
diff --git a/src/sss_client/pam_sss.c b/src/sss_client/pam_sss.c
index 6916575bb6d8288508ce1894d272356ffcc23f04..83769a15337f7ba027f66a8f7e530daa4a287383 100644
--- a/src/sss_client/pam_sss.c
+++ b/src/sss_client/pam_sss.c
@@ -41,6 +41,7 @@
#include "sss_cli.h"
#include "util/atomic_io.h"
+#include "util/authtok-utils.h"
#include <libintl.h>
#define _(STRING) dgettext (PACKAGE, STRING)
diff --git a/src/sss_client/sss_cli.h b/src/sss_client/sss_cli.h
index d508a0671cd1b3ee087e0967f7015628ceabe20f..9a19d7d47d0a9d7dabeac36dc2c866c3420ef501 100644
--- a/src/sss_client/sss_cli.h
+++ b/src/sss_client/sss_cli.h
@@ -301,6 +301,9 @@ enum sss_authtok_type {
* a Kerberos credential cache file,
* it may or may no contain
* a trailing \\0 */
+ SSS_AUTHTOK_TYPE_2FA = 0x0003, /**< Authentication token has two
+ * factors, they may or may no contain
+ * a trailing \\0 */
};
/**
diff --git a/src/tests/cmocka/test_authtok.c b/src/tests/cmocka/test_authtok.c
index 0c7b7197fb2c03d69dc4df2310229ea100ad28d4..65828b82d5c8aa389df7db80df3deefe79f14a8b 100644
--- a/src/tests/cmocka/test_authtok.c
+++ b/src/tests/cmocka/test_authtok.c
@@ -311,6 +311,152 @@ static void test_sss_authtok_copy(void **state)
talloc_free(data);
}
+void test_sss_authtok_2fa(void **state)
+{
+ int ret;
+ const char *fa1;
+ size_t fa1_size;
+ const char *fa2;
+ size_t fa2_size;
+ struct test_state *ts;
+
+ ts = talloc_get_type_abort(*state, struct test_state);
+
+ ret = sss_authtok_set_2fa(NULL, "a", 0, "b", 0);
+ assert_int_equal(ret, EINVAL);
+
+ /* Test missing first factor */
+ ret = sss_authtok_set_2fa(ts->authtoken, NULL, 1, "b", 1);
+ assert_int_equal(ret, EINVAL);
+ /* Test missing second factor */
+ ret = sss_authtok_set_2fa(ts->authtoken, "a", 1, NULL, 1);
+ assert_int_equal(ret, EINVAL);
+ /* Test wrong first factor length */
+ ret = sss_authtok_set_2fa(ts->authtoken, "ab", 1, "b", 1);
+ assert_int_equal(ret, EINVAL);
+ /* Test wrong second factor length */
+ ret = sss_authtok_set_2fa(ts->authtoken, "a", 1, "bc", 1);
+ assert_int_equal(ret, EINVAL);
+
+ ret = sss_authtok_set_2fa(ts->authtoken, "a", 1, "bc", 2);
+ assert_int_equal(ret, EOK);
+ assert_int_equal(sss_authtok_get_size(ts->authtoken),
+ 2 * sizeof(uint32_t) + 5);
+ assert_int_equal(sss_authtok_get_type(ts->authtoken), SSS_AUTHTOK_TYPE_2FA);
+ assert_memory_equal(sss_authtok_get_data(ts->authtoken),
+ "\2\0\0\0\3\0\0\0a\0bc\0",
+ 2 * sizeof(uint32_t) + 5);
+
+ ret = sss_authtok_get_2fa(ts->authtoken, &fa1, &fa1_size, &fa2, &fa2_size);
+ assert_int_equal(ret, EOK);
+ assert_int_equal(fa1_size, 1);
+ assert_string_equal(fa1, "a");
+ assert_int_equal(fa2_size, 2);
+ assert_string_equal(fa2, "bc");
+
+ sss_authtok_set_empty(ts->authtoken);
+
+ /* check return code of empty token */
+ ret = sss_authtok_get_2fa(ts->authtoken, &fa1, &fa1_size, &fa2, &fa2_size);
+ assert_int_equal(ret, ENOENT);
+
+ /* check return code for other token type */
+ ret = sss_authtok_set_password(ts->authtoken, "abc", 0);
+ ret = sss_authtok_get_2fa(ts->authtoken, &fa1, &fa1_size, &fa2, &fa2_size);
+ assert_int_equal(ret, EACCES);
+
+ sss_authtok_set_empty(ts->authtoken);
+
+ /* check return code for garbage */
+ ret = sss_authtok_set(ts->authtoken, SSS_AUTHTOK_TYPE_2FA,
+ (const uint8_t *) "1111222233334444", 16);
+ assert_int_equal(ret, EINVAL);
+
+ sss_authtok_set_empty(ts->authtoken);
+}
+
+void test_sss_authtok_2fa_blobs(void **state)
+{
+ int ret;
+ struct test_state *ts;
+ size_t needed_size;
+ uint8_t *buf;
+ char *fa1;
+ size_t fa1_len;
+ char *fa2;
+ size_t fa2_len;
+
+ ts = talloc_get_type_abort(*state, struct test_state);
+
+ ret = sss_auth_pack_2fa_blob("abc", 0, "defg", 0, NULL, 0, &needed_size);
+ assert_int_equal(ret, EAGAIN);
+
+ buf = talloc_size(ts, needed_size);
+ assert_non_null(buf);
+
+ ret = sss_auth_pack_2fa_blob("abc", 0, "defg", 0, buf, needed_size,
+ &needed_size);
+ assert_int_equal(ret, EOK);
+
+ assert_memory_equal(buf, "\4\0\0\0\5\0\0\0abc\0defg\0", needed_size);
+
+ ret = sss_auth_unpack_2fa_blob(ts, buf, needed_size, &fa1, &fa1_len, &fa2,
+ &fa2_len);
+ assert_int_equal(ret, EOK);
+ assert_int_equal(fa1_len, 3);
+ assert_string_equal(fa1, "abc");
+ assert_int_equal(fa2_len, 4);
+ assert_string_equal(fa2, "defg");
+
+ talloc_free(buf);
+ talloc_free(fa1);
+ talloc_free(fa2);
+}
+
+#define MISSING_NULL_CHECK do { \
+ assert_int_equal(ret, EOK); \
+ assert_int_equal(fa1_len, 3); \
+ assert_string_equal(fa1, "abc"); \
+ assert_int_equal(fa2_len, 4); \
+ assert_string_equal(fa2, "defg"); \
+ \
+ talloc_free(fa1); \
+ talloc_free(fa2); \
+} while (0)
+
+void test_sss_authtok_2fa_blobs_missing_null(void **state)
+{
+ int ret;
+ struct test_state *ts;
+ char *fa1;
+ size_t fa1_len;
+ char *fa2;
+ size_t fa2_len;
+ uint8_t b0[] = {0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 'a', 'b', 'c', 0x00, 'd', 'e', 'f', 'g', 0x00};
+ uint8_t b1[] = {0x03, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 'a', 'b', 'c', 'd', 'e', 'f', 'g', 0x00};
+ uint8_t b2[] = {0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 'a', 'b', 'c', 0x00, 'd', 'e', 'f', 'g'};
+ uint8_t b3[] = {0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 'a', 'b', 'c', 'd', 'e', 'f', 'g'};
+
+
+ ts = talloc_get_type_abort(*state, struct test_state);
+
+ ret = sss_auth_unpack_2fa_blob(ts, b0, sizeof(b0), &fa1, &fa1_len, &fa2,
+ &fa2_len);
+ MISSING_NULL_CHECK;
+
+ ret = sss_auth_unpack_2fa_blob(ts, b1, sizeof(b1), &fa1, &fa1_len, &fa2,
+ &fa2_len);
+ MISSING_NULL_CHECK;
+
+ ret = sss_auth_unpack_2fa_blob(ts, b2, sizeof(b2), &fa1, &fa1_len, &fa2,
+ &fa2_len);
+ MISSING_NULL_CHECK;
+
+ ret = sss_auth_unpack_2fa_blob(ts, b3, sizeof(b3), &fa1, &fa1_len, &fa2,
+ &fa2_len);
+ MISSING_NULL_CHECK;
+}
+
int main(int argc, const char *argv[])
{
poptContext pc;
@@ -333,7 +479,13 @@ int main(int argc, const char *argv[])
cmocka_unit_test_setup_teardown(test_sss_authtok_wipe_password,
setup, teardown),
cmocka_unit_test_setup_teardown(test_sss_authtok_copy,
- setup, teardown)
+ setup, teardown),
+ cmocka_unit_test_setup_teardown(test_sss_authtok_2fa,
+ setup, teardown),
+ cmocka_unit_test_setup_teardown(test_sss_authtok_2fa_blobs,
+ setup, teardown),
+ cmocka_unit_test_setup_teardown(test_sss_authtok_2fa_blobs_missing_null,
+ setup, teardown),
};
/* Set debug level to invalid value so we can deside if -d 0 was used. */
diff --git a/src/util/authtok-utils.c b/src/util/authtok-utils.c
new file mode 100644
index 0000000000000000000000000000000000000000..3955e6667f0957fa456d8a33d0325299b56f8246
--- /dev/null
+++ b/src/util/authtok-utils.c
@@ -0,0 +1,74 @@
+/*
+ SSSD - auth utils helpers
+
+ Copyright (C) Sumit Bose <sbose at redhat.com> 2015
+
+ 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 <http://www.gnu.org/licenses/>.
+*/
+
+/* This file is use by SSSD clients and the main daemons. Please do not add
+ * code which is specific to only one of them. */
+
+#include <errno.h>
+
+#include "sss_client/sss_cli.h"
+
+errno_t sss_auth_pack_2fa_blob(const char *fa1, size_t fa1_len,
+ const char *fa2, size_t fa2_len,
+ uint8_t *buf, size_t buf_len,
+ size_t *_2fa_blob_len)
+{
+ size_t c;
+ uint32_t tmp_uint32_t;
+
+ if (fa1 == NULL || fa1_len > UINT32_MAX || fa2 == NULL
+ || fa2_len > UINT32_MAX || (buf == NULL && buf_len != 0)) {
+ return EINVAL;
+ }
+
+ if (fa1_len == 0) {
+ fa1_len = strlen(fa1);
+ } else {
+ if (fa1[fa1_len] != '\0') {
+ return EINVAL;
+ }
+ }
+
+ if (fa2_len == 0) {
+ fa2_len = strlen(fa2);
+ } else {
+ if (fa2[fa2_len] != '\0') {
+ return EINVAL;
+ }
+ }
+
+ *_2fa_blob_len = fa1_len + fa2_len + 2 + 2 * sizeof(uint32_t);
+ if (buf_len < *_2fa_blob_len) {
+ return EAGAIN;
+ }
+
+ c = 0;
+ tmp_uint32_t = (uint32_t) fa1_len + 1;
+ SAFEALIGN_COPY_UINT32(buf, &tmp_uint32_t, &c);
+ tmp_uint32_t = (uint32_t) fa2_len + 1;
+ SAFEALIGN_COPY_UINT32(buf + c, &tmp_uint32_t, &c);
+
+ memcpy(buf + c, fa1, fa1_len + 1);
+ c += fa1_len + 1;
+
+ memcpy(buf + c, fa2, fa2_len + 1);
+
+ return 0;
+}
+
diff --git a/src/util/authtok-utils.h b/src/util/authtok-utils.h
new file mode 100644
index 0000000000000000000000000000000000000000..6ce294891a74fd66940e6f4eee5056beba697ee6
--- /dev/null
+++ b/src/util/authtok-utils.h
@@ -0,0 +1,55 @@
+/*
+ SSSD - auth utils helpers
+
+ Copyright (C) Sumit Bose <simo at redhat.com> 2015
+
+ 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 <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef __AUTHTOK_UTILS_H__
+#define __AUTHTOK_UTILS_H__
+
+#include <talloc.h>
+
+#include "sss_client/sss_cli.h"
+
+/**
+ * @brief Fill memory buffer with 2FA blob
+ *
+ * @param[in] fa1 First authentication factor, null terminated
+ * @param[in] fa1_len Length of the first authentication factor, if 0
+ * strlen() will be called internally
+ * @param[in] fa2 Second authentication factor, null terminated
+ * @param[in] fa2_len Length of the second authentication factor, if 0
+ * strlen() will be called internally
+ * @param[in] buf memory buffer of size buf_len
+ * @param[in] buf_len size of memory buffer buf
+ *
+ * @param[out] _2fa_blob_len size of the 2FA blob
+ *
+ * @return EOK on success
+ * EINVAL if input data is not consistent
+ * EAGAIN if provided buffer is too small, _2fa_blob_len
+ * contains the size needed to store the 2FA blob
+ */
+errno_t sss_auth_pack_2fa_blob(const char *fa1, size_t fa1_len,
+ const char *fa2, size_t fa2_len,
+ uint8_t *buf, size_t buf_len,
+ size_t *_2fa_blob_len);
+
+errno_t sss_auth_unpack_2fa_blob(TALLOC_CTX *mem_ctx,
+ const uint8_t *blob, size_t blob_len,
+ char **fa1, size_t *_fa1_len,
+ char **fa2, size_t *_fa2_len);
+#endif /* __AUTHTOK_UTILS_H__ */
diff --git a/src/util/authtok.c b/src/util/authtok.c
index b7bc17ed0cdc5cfee7f455b0d7047803e628274a..0276ecc57e90acd09e9dbee080ac12a40ce6ea6a 100644
--- a/src/util/authtok.c
+++ b/src/util/authtok.c
@@ -38,6 +38,7 @@ size_t sss_authtok_get_size(struct sss_auth_token *tok)
switch (tok->type) {
case SSS_AUTHTOK_TYPE_PASSWORD:
case SSS_AUTHTOK_TYPE_CCFILE:
+ case SSS_AUTHTOK_TYPE_2FA:
return tok->length;
case SSS_AUTHTOK_TYPE_EMPTY:
return 0;
@@ -70,6 +71,7 @@ errno_t sss_authtok_get_password(struct sss_auth_token *tok,
}
return EOK;
case SSS_AUTHTOK_TYPE_CCFILE:
+ case SSS_AUTHTOK_TYPE_2FA:
return EACCES;
}
@@ -92,6 +94,7 @@ errno_t sss_authtok_get_ccfile(struct sss_auth_token *tok,
}
return EOK;
case SSS_AUTHTOK_TYPE_PASSWORD:
+ case SSS_AUTHTOK_TYPE_2FA:
return EACCES;
}
@@ -140,6 +143,7 @@ void sss_authtok_set_empty(struct sss_auth_token *tok)
case SSS_AUTHTOK_TYPE_EMPTY:
return;
case SSS_AUTHTOK_TYPE_PASSWORD:
+ case SSS_AUTHTOK_TYPE_2FA:
safezero(tok->data, tok->length);
break;
case SSS_AUTHTOK_TYPE_CCFILE:
@@ -169,6 +173,9 @@ errno_t sss_authtok_set_ccfile(struct sss_auth_token *tok,
"ccfile", ccfile, len);
}
+static errno_t sss_authtok_set_2fa_from_blob(struct sss_auth_token *tok,
+ const uint8_t *data, size_t len);
+
errno_t sss_authtok_set(struct sss_auth_token *tok,
enum sss_authtok_type type,
const uint8_t *data, size_t len)
@@ -178,6 +185,8 @@ errno_t sss_authtok_set(struct sss_auth_token *tok,
return sss_authtok_set_password(tok, (const char *)data, len);
case SSS_AUTHTOK_TYPE_CCFILE:
return sss_authtok_set_ccfile(tok, (const char *)data, len);
+ case SSS_AUTHTOK_TYPE_2FA:
+ return sss_authtok_set_2fa_from_blob(tok, data, len);
case SSS_AUTHTOK_TYPE_EMPTY:
sss_authtok_set_empty(tok);
return EOK;
@@ -230,3 +239,176 @@ void sss_authtok_wipe_password(struct sss_auth_token *tok)
safezero(tok->data, tok->length);
}
+errno_t sss_auth_unpack_2fa_blob(TALLOC_CTX *mem_ctx,
+ const uint8_t *blob, size_t blob_len,
+ char **fa1, size_t *_fa1_len,
+ char **fa2, size_t *_fa2_len)
+{
+ size_t c;
+ uint32_t fa1_len;
+ uint32_t fa2_len;
+
+ if (blob_len < 2 * sizeof(uint32_t)) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Blob too small.\n");
+ return EINVAL;
+ }
+
+ c = 0;
+ SAFEALIGN_COPY_UINT32(&fa1_len, blob, &c);
+ SAFEALIGN_COPY_UINT32(&fa2_len, blob + c, &c);
+
+ if (blob_len != 2 * sizeof(uint32_t) + fa1_len + fa2_len) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Blob size mismatch.\n");
+ return EINVAL;
+ }
+
+ if (fa1_len != 0) {
+ *fa1 = talloc_strndup(mem_ctx, (const char *) blob + c, fa1_len);
+ if (*fa1 == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "talloc_strndup failed.\n");
+ return ENOMEM;
+ }
+ } else {
+ *fa1 = NULL;
+ }
+
+ if (fa2_len != 0) {
+ *fa2 = talloc_strndup(mem_ctx, (const char *) blob + c + fa1_len,
+ fa2_len);
+ if (*fa2 == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "talloc_strndup failed.\n");
+ talloc_free(*fa1);
+ return ENOMEM;
+ }
+ } else {
+ *fa2 = NULL;
+ }
+
+ /* Re-calculate length for the case where \0 was missing in the blob */
+ *_fa1_len = (*fa1 == NULL) ? 0 : strlen(*fa1);
+ *_fa2_len = (*fa2 == NULL) ? 0 : strlen(*fa2);
+
+ return EOK;
+}
+
+static errno_t sss_authtok_set_2fa_from_blob(struct sss_auth_token *tok,
+ const uint8_t *data, size_t len)
+{
+ TALLOC_CTX *tmp_ctx;
+ int ret;
+ char *fa1;
+ size_t fa1_len;
+ char *fa2;
+ size_t fa2_len;
+
+ tmp_ctx = talloc_new(NULL);
+ if (tmp_ctx == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "talloc_new failed.\n");
+ ret = ENOMEM;
+ goto done;
+ }
+
+ ret = sss_auth_unpack_2fa_blob(tmp_ctx, data, len, &fa1, &fa1_len,
+ &fa2, &fa2_len);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE, "sss_auth_unpack_2fa_blob failed.\n");
+ goto done;
+ }
+
+ ret = sss_authtok_set_2fa(tok, fa1, fa1_len, fa2, fa2_len);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE, "sss_authtok_set_2fa failed.\n");
+ goto done;
+ }
+
+ ret = EOK;
+done:
+ talloc_free(tmp_ctx);
+
+ if (ret != EOK) {
+ sss_authtok_set_empty(tok);
+ }
+
+ return ret;
+}
+
+
+errno_t sss_authtok_get_2fa(struct sss_auth_token *tok,
+ const char **fa1, size_t *fa1_len,
+ const char **fa2, size_t *fa2_len)
+{
+ size_t c;
+ uint32_t tmp_uint32_t;
+
+ if (tok->type != SSS_AUTHTOK_TYPE_2FA) {
+ return (tok->type == SSS_AUTHTOK_TYPE_EMPTY) ? ENOENT : EACCES;
+ }
+
+ if (tok->length < 2 * sizeof(uint32_t)) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Blob too small.\n");
+ return EINVAL;
+ }
+
+ c = 0;
+ SAFEALIGN_COPY_UINT32(&tmp_uint32_t, tok->data, &c);
+ *fa1_len = tmp_uint32_t - 1;
+ SAFEALIGN_COPY_UINT32(&tmp_uint32_t, tok->data + c, &c);
+ *fa2_len = tmp_uint32_t - 1;
+
+ if (*fa1_len == 0 || fa2_len == 0
+ || tok->length != 2 * sizeof(uint32_t) + *fa1_len + *fa2_len + 2) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Blob size mismatch.\n");
+ return EINVAL;
+ }
+
+ if (tok->data[c + *fa1_len] != '\0'
+ || tok->data[c + *fa1_len + 1 + *fa2_len] != '\0') {
+ DEBUG(SSSDBG_CRIT_FAILURE, "Missing terminating null character.\n");
+ return EINVAL;
+ }
+
+ *fa1 = (const char *) tok->data + c;
+ *fa2 = (const char *) tok->data + c + *fa1_len + 1;
+
+ return EOK;
+}
+
+errno_t sss_authtok_set_2fa(struct sss_auth_token *tok,
+ const char *fa1, size_t fa1_len,
+ const char *fa2, size_t fa2_len)
+{
+ int ret;
+ size_t needed_size;
+
+ if (tok == NULL) {
+ return EINVAL;
+ }
+
+ sss_authtok_set_empty(tok);
+
+ ret = sss_auth_pack_2fa_blob(fa1, fa1_len, fa2, fa2_len, NULL, 0,
+ &needed_size);
+ if (ret != EAGAIN) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "sss_auth_pack_2fa_blob unexpectedly returned [%d].\n", ret);
+ return EINVAL;
+ }
+
+ tok->data = talloc_size(tok, needed_size);
+ if (tok->data == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "talloc_size failed.\n");
+ return ENOMEM;
+ }
+
+ ret = sss_auth_pack_2fa_blob(fa1, fa1_len, fa2, fa2_len, tok->data,
+ needed_size, &needed_size);
+ if (ret != EOK) {
+ talloc_free(tok->data);
+ DEBUG(SSSDBG_OP_FAILURE, "sss_auth_pack_2fa_blob failed.\n");
+ return ret;
+ }
+ tok->length = needed_size;
+ tok->type = SSS_AUTHTOK_TYPE_2FA;
+
+ return EOK;
+}
diff --git a/src/util/authtok.h b/src/util/authtok.h
index 1f6def4c3b6a1cbf6c4f34bb76c496ddae6f9d5f..e06ec2c07b68dfcc5dc6582bec6a5644ab3d2648 100644
--- a/src/util/authtok.h
+++ b/src/util/authtok.h
@@ -21,6 +21,7 @@
#define __AUTHTOK_H__
#include "util/util.h"
+#include "util/authtok-utils.h"
#include "sss_client/sss_cli.h"
/* Use sss_authtok_* accesor functions instead of struct sss_auth_token
@@ -179,4 +180,47 @@ void sss_authtok_wipe_password(struct sss_auth_token *tok);
*/
struct sss_auth_token *sss_authtok_new(TALLOC_CTX *mem_ctx);
+/**
+ * @brief Set authtoken with 2FA data
+ *
+ * @param tok A pointer to a sss_auth_token structure to change, also
+ * used as a memory context to allocate the internal data.
+ * @param[in] fa1 First authentication factor, null terminated
+ * @param[in] fa1_len Length of the first authentication factor, if 0
+ * strlen() will be called internally
+ * @param[in] fa2 Second authentication factor, null terminated
+ * @param[in] fa2_len Length of the second authentication factor, if 0
+ * strlen() will be called internally
+ *
+ * @return EOK on success
+ * ENOMEM if memory allocation failed
+ * EINVAL if input data is not consistent
+ */
+errno_t sss_authtok_set_2fa(struct sss_auth_token *tok,
+ const char *fa1, size_t fa1_len,
+ const char *fa2, size_t fa2_len);
+
+/**
+ * @brief Get 2FA factors from authtoken
+ *
+ * @param tok A pointer to a sss_auth_token structure to change, also
+ * used as a memory context to allocate the internal data.
+ * @param[out] fa1 A pointer to a const char *, that will point to a
+ * null terminated string holding the first
+ * authentication factor
+ * @param[out] fa1_len Length of the first authentication factor
+ * @param[out] fa2 A pointer to a const char *, that will point to a
+ * null terminated string holding the second
+ * authentication factor
+ * @param[out] fa2_len Length of the second authentication factor
+ *
+ * @return EOK on success
+ * ENOMEM if memory allocation failed
+ * EINVAL if input data is not consistent
+ * ENOENT if the token is empty
+ * EACCESS if the token is not a 2FA token
+ */
+errno_t sss_authtok_get_2fa(struct sss_auth_token *tok,
+ const char **fa1, size_t *fa1_len,
+ const char **fa2, size_t *fa2_len);
#endif /* __AUTHTOK_H__ */
--
2.1.0
-------------- next part --------------
From 84f05d84ef5bd88a6f1e5c81bc7bd7f2108bacd1 Mon Sep 17 00:00:00 2001
From: Sumit Bose <sbose at redhat.com>
Date: Thu, 8 Jan 2015 17:10:42 +0100
Subject: [PATCH 03/14] pam: handle 2FA authentication token in the responder
---
src/responder/pam/pamsrv_cmd.c | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/src/responder/pam/pamsrv_cmd.c b/src/responder/pam/pamsrv_cmd.c
index 0eb78c723c0c066a9a2795f4ae8de85f18fcb2da..dd6574db7e0339aad16ba5806327e117e1735054 100644
--- a/src/responder/pam/pamsrv_cmd.c
+++ b/src/responder/pam/pamsrv_cmd.c
@@ -143,6 +143,10 @@ static int extract_authtok_v2(struct sss_auth_token *tok,
auth_token_length);
}
break;
+ case SSS_AUTHTOK_TYPE_2FA:
+ ret = sss_authtok_set(tok, SSS_AUTHTOK_TYPE_2FA,
+ auth_token_data, auth_token_length);
+ break;
default:
return EINVAL;
}
--
2.1.0
-------------- next part --------------
From c4d23d29adf9e7c554746be4b9faf5718861dc82 Mon Sep 17 00:00:00 2001
From: Sumit Bose <sbose at redhat.com>
Date: Tue, 24 Mar 2015 17:24:50 +0100
Subject: [PATCH 04/14] Add pre-auth request
---
src/providers/data_provider_be.c | 1 +
src/providers/dp_pam_data_util.c | 2 ++
src/providers/ipa/ipa_auth.c | 1 +
src/providers/krb5/krb5_auth.c | 2 ++
src/responder/pam/pamsrv_cmd.c | 7 +++++++
src/sss_client/sss_cli.h | 4 ++++
6 files changed, 17 insertions(+)
diff --git a/src/providers/data_provider_be.c b/src/providers/data_provider_be.c
index a48a42878d207785b664c06d3a6c1557dcab69b3..a37fbbc8d72d8bc0bf31b9b05a4aaf24c2f4c9e6 100644
--- a/src/providers/data_provider_be.c
+++ b/src/providers/data_provider_be.c
@@ -1374,6 +1374,7 @@ static int be_pam_handler(struct sbus_request *dbus_req, void *user_data)
switch (pd->cmd) {
case SSS_PAM_AUTHENTICATE:
+ case SSS_PAM_PREAUTH:
target = BET_AUTH;
break;
case SSS_PAM_ACCT_MGMT:
diff --git a/src/providers/dp_pam_data_util.c b/src/providers/dp_pam_data_util.c
index 313948b369cf605c91eb608b9a394d32a1e128d1..8724bf936f3f46fb8393c8a3da57215a73b4191a 100644
--- a/src/providers/dp_pam_data_util.c
+++ b/src/providers/dp_pam_data_util.c
@@ -43,6 +43,8 @@ static const char *pamcmd2str(int cmd) {
return "PAM_CHAUTHTOK";
case SSS_PAM_CHAUTHTOK_PRELIM:
return "PAM_CHAUTHTOK_PRELIM";
+ case SSS_PAM_PREAUTH:
+ return "SSS_PAM_PREAUTH";
default:
return "UNKNOWN";
}
diff --git a/src/providers/ipa/ipa_auth.c b/src/providers/ipa/ipa_auth.c
index 223448338ef7ad86259657b5bea2e47911b824cb..79e891b77205f75671c146af0192b764f12e13b0 100644
--- a/src/providers/ipa/ipa_auth.c
+++ b/src/providers/ipa/ipa_auth.c
@@ -208,6 +208,7 @@ void ipa_auth(struct be_req *be_req)
switch (state->pd->cmd) {
case SSS_PAM_AUTHENTICATE:
+ case SSS_PAM_PREAUTH:
state->ipa_auth_ctx = talloc_get_type(
be_ctx->bet_info[BET_AUTH].pvt_bet_data,
struct ipa_auth_ctx);
diff --git a/src/providers/krb5/krb5_auth.c b/src/providers/krb5/krb5_auth.c
index 25caf7b788a3f373f47e9d8aad38a2ea6fc12621..5ce45b1579f93d618da455b7ab2687c078332067 100644
--- a/src/providers/krb5/krb5_auth.c
+++ b/src/providers/krb5/krb5_auth.c
@@ -441,6 +441,8 @@ struct tevent_req *krb5_auth_send(TALLOC_CTX *mem_ctx,
goto done;
}
break;
+ case SSS_PAM_PREAUTH:
+ break;
default:
DEBUG(SSSDBG_CONF_SETTINGS, "Unexpected pam task %d.\n", pd->cmd);
state->pam_status = PAM_SYSTEM_ERR;
diff --git a/src/responder/pam/pamsrv_cmd.c b/src/responder/pam/pamsrv_cmd.c
index dd6574db7e0339aad16ba5806327e117e1735054..f84349d1f60e4e7c858b9bc6303ade3a962172d1 100644
--- a/src/responder/pam/pamsrv_cmd.c
+++ b/src/responder/pam/pamsrv_cmd.c
@@ -1455,6 +1455,12 @@ static int pam_cmd_chauthtok_prelim(struct cli_ctx *cctx) {
return pam_forwarder(cctx, SSS_PAM_CHAUTHTOK_PRELIM);
}
+static int pam_cmd_preauth(struct cli_ctx *cctx) {
+ DEBUG(SSSDBG_CONF_SETTINGS, "entering pam_cmd_preauth\n");
+ return pam_forwarder(cctx, SSS_PAM_PREAUTH);
+}
+
+
struct cli_protocol_version *register_cli_protocol_version(void)
{
static struct cli_protocol_version pam_cli_protocol_version[] = {
@@ -1478,6 +1484,7 @@ struct sss_cmd_table *get_pam_cmds(void)
{SSS_PAM_CLOSE_SESSION, pam_cmd_close_session},
{SSS_PAM_CHAUTHTOK, pam_cmd_chauthtok},
{SSS_PAM_CHAUTHTOK_PRELIM, pam_cmd_chauthtok_prelim},
+ {SSS_PAM_PREAUTH, pam_cmd_preauth},
{SSS_CLI_NULL, NULL}
};
diff --git a/src/sss_client/sss_cli.h b/src/sss_client/sss_cli.h
index 9a19d7d47d0a9d7dabeac36dc2c866c3420ef501..2895659b9c3ed4ab520ca90846379c22fd9567f7 100644
--- a/src/sss_client/sss_cli.h
+++ b/src/sss_client/sss_cli.h
@@ -220,6 +220,10 @@ enum sss_cli_command {
SSS_CMD_RENEW = 0x00F8, /**< Renew a credential with a limited
* lifetime, e.g. a Kerberos Ticket
* Granting Ticket (TGT) */
+ SSS_PAM_PREAUTH = 0x00F9, /**< Request which can be run before
+ * an authentication request to find
+ * out which authentication methods
+ * are available for the given user. */
/* PAC responder calls */
SSS_PAC_ADD_PAC_USER = 0x0101,
--
2.1.0
-------------- next part --------------
From 6fbeeba4eb1543620a8b3dc3bee7894253ccb924 Mon Sep 17 00:00:00 2001
From: Sumit Bose <sbose at redhat.com>
Date: Tue, 24 Mar 2015 17:26:53 +0100
Subject: [PATCH 05/14] krb5-child: add preauth and split 2fa token support
---
src/providers/krb5/krb5_auth.c | 3 +-
src/providers/krb5/krb5_child.c | 289 ++++++++++++++++++++++++++++----
src/providers/krb5/krb5_child_handler.c | 4 +
src/sss_client/sss_cli.h | 6 +
4 files changed, 268 insertions(+), 34 deletions(-)
diff --git a/src/providers/krb5/krb5_auth.c b/src/providers/krb5/krb5_auth.c
index 5ce45b1579f93d618da455b7ab2687c078332067..651a9201756be8bc80199636b494b2773cff9c3e 100644
--- a/src/providers/krb5/krb5_auth.c
+++ b/src/providers/krb5/krb5_auth.c
@@ -394,7 +394,8 @@ struct tevent_req *krb5_auth_send(TALLOC_CTX *mem_ctx,
switch (pd->cmd) {
case SSS_PAM_AUTHENTICATE:
case SSS_PAM_CHAUTHTOK:
- if (authtok_type != SSS_AUTHTOK_TYPE_PASSWORD) {
+ if (authtok_type != SSS_AUTHTOK_TYPE_PASSWORD
+ && authtok_type != SSS_AUTHTOK_TYPE_2FA) {
/* handle empty password gracefully */
if (authtok_type == SSS_AUTHTOK_TYPE_EMPTY) {
DEBUG(SSSDBG_CRIT_FAILURE,
diff --git a/src/providers/krb5/krb5_child.c b/src/providers/krb5/krb5_child.c
index 4164e75c9918392d0f37516853896436bcda2459..a3977b399d3a36c79a42993ae060f1f51ff30dd4 100644
--- a/src/providers/krb5/krb5_child.c
+++ b/src/providers/krb5/krb5_child.c
@@ -54,6 +54,9 @@ struct krb5_req {
char* name;
krb5_creds *creds;
bool otp;
+ char *otp_vendor;
+ char *otp_token_id;
+ char *otp_challenge;
krb5_get_init_creds_opt *options;
struct pam_data *pd;
@@ -268,7 +271,88 @@ static int token_pin_destructor(char *mem)
return 0;
}
-static krb5_error_code tokeninfo_matches(TALLOC_CTX *mem_ctx,
+static krb5_error_code tokeninfo_matches_2fa(TALLOC_CTX *mem_ctx,
+ const krb5_responder_otp_tokeninfo *ti,
+ const char *fa1, size_t fa1_len,
+ const char *fa2, size_t fa2_len,
+ char **out_token, char **out_pin)
+{
+ char *token = NULL, *pin = NULL;
+ checker check = NULL;
+ int i;
+
+
+ if (ti->flags & KRB5_RESPONDER_OTP_FLAGS_NEXTOTP) {
+ return ENOTSUP;
+ }
+
+ if (ti->challenge != NULL) {
+ return ENOTSUP;
+ }
+
+ /* This is a non-sensical value. */
+ if (ti->length == 0) {
+ return EPROTO;
+ }
+
+ if (ti->flags & KRB5_RESPONDER_OTP_FLAGS_COLLECT_TOKEN) {
+ if (ti->length > 0 && ti->length != fa2_len) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Expected [%d] and given [%zu] token size " \
+ "do not match.\n", ti->length, fa2_len);
+ return EMSGSIZE;
+ }
+
+ if (ti->flags & KRB5_RESPONDER_OTP_FLAGS_COLLECT_PIN) {
+ if (ti->flags & KRB5_RESPONDER_OTP_FLAGS_SEPARATE_PIN) {
+
+ pin = talloc_strndup(mem_ctx, fa1, fa1_len);
+ if (pin == NULL) {
+ talloc_free(token);
+ return ENOMEM;
+ }
+ talloc_set_destructor(pin, token_pin_destructor);
+
+ token = talloc_strndup(mem_ctx, fa2, fa2_len);
+ if (token == NULL) {
+ return ENOMEM;
+ }
+ talloc_set_destructor(token, token_pin_destructor);
+
+ check = pick_checker(ti->format);
+ }
+ } else {
+ token = talloc_asprintf(mem_ctx, "%s%s", fa1, fa2);
+ if (token == NULL) {
+ return ENOMEM;
+ }
+ talloc_set_destructor(token, token_pin_destructor);
+
+ check = pick_checker(ti->format);
+ }
+ } else {
+ /* Assuming PIN only required */
+ pin = talloc_strndup(mem_ctx, fa1, fa1_len);
+ if (pin == NULL) {
+ return ENOMEM;
+ }
+ talloc_set_destructor(pin, token_pin_destructor);
+ }
+
+ /* If check is set, we need to verify the contents of the token. */
+ for (i = 0; check != NULL && token[i] != '\0'; i++) {
+ if (!check(token[i])) {
+ talloc_free(token);
+ talloc_free(pin);
+ return EBADMSG;
+ }
+ }
+
+ *out_token = token;
+ *out_pin = pin;
+ return 0;
+}
+static krb5_error_code tokeninfo_matches_pwd(TALLOC_CTX *mem_ctx,
const krb5_responder_otp_tokeninfo *ti,
const char *pwd, size_t len,
char **out_token, char **out_pin)
@@ -364,15 +448,52 @@ static krb5_error_code tokeninfo_matches(TALLOC_CTX *mem_ctx,
return 0;
}
+static krb5_error_code tokeninfo_matches(TALLOC_CTX *mem_ctx,
+ const krb5_responder_otp_tokeninfo *ti,
+ struct sss_auth_token *auth_tok,
+ char **out_token, char **out_pin)
+{
+ int ret;
+ const char *pwd;
+ size_t len;
+ const char *fa2;
+ size_t fa2_len;
+
+ switch (sss_authtok_get_type(auth_tok)) {
+ case SSS_AUTHTOK_TYPE_PASSWORD:
+ ret = sss_authtok_get_password(auth_tok, &pwd, &len);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE, "sss_authtok_get_password failed.\n");
+ return ret;
+ }
+
+ return tokeninfo_matches_pwd(mem_ctx, ti, pwd, len, out_token, out_pin);
+ break;
+ case SSS_AUTHTOK_TYPE_2FA:
+ ret = sss_authtok_get_2fa(auth_tok, &pwd, &len, &fa2, &fa2_len);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE, "sss_authtok_get_2fa failed.\n");
+ return ret;
+ }
+
+ return tokeninfo_matches_2fa(mem_ctx, ti, pwd, len, fa2, fa2_len,
+ out_token, out_pin);
+ break;
+ default:
+ DEBUG(SSSDBG_CRIT_FAILURE, "Unsupported authtok type.\n");
+ }
+
+ return EINVAL;
+}
+
static krb5_error_code answer_otp(krb5_context ctx,
struct krb5_req *kr,
krb5_responder_context rctx)
{
krb5_responder_otp_challenge *chl;
char *token = NULL, *pin = NULL;
- const char *pwd = NULL;
krb5_error_code ret;
- size_t i, len;
+ size_t i;
ret = krb5_responder_otp_get_challenge(ctx, rctx, &chl);
if (ret != EOK || chl == NULL) {
@@ -388,14 +509,37 @@ static krb5_error_code answer_otp(krb5_context ctx,
kr->otp = true;
- /* Validate our assumptions about the contents of authtok. */
- ret = sss_authtok_get_password(kr->pd->authtok, &pwd, &len);
- if (ret != EOK)
- goto done;
+ if (kr->pd->cmd == SSS_PAM_PREAUTH) {
+ for (i = 0; chl->tokeninfo[i] != NULL; i++) {
+ DEBUG(SSSDBG_TRACE_ALL, "[%zu] Vendor [%s].\n",
+ i, chl->tokeninfo[i]->vendor);
+ DEBUG(SSSDBG_TRACE_ALL, "[%zu] Token-ID [%s].\n",
+ i, chl->tokeninfo[i]->token_id);
+ DEBUG(SSSDBG_TRACE_ALL, "[%zu] Challenge [%s].\n",
+ i, chl->tokeninfo[i]->challenge);
+ DEBUG(SSSDBG_TRACE_ALL, "[%zu] Flags [%d].\n",
+ i, chl->tokeninfo[i]->flags);
+ }
+
+ if (chl->tokeninfo[0]->vendor != NULL) {
+ kr->otp_vendor = talloc_strdup(kr, chl->tokeninfo[i]->vendor);
+ }
+ if (chl->tokeninfo[0]->token_id != NULL) {
+ kr->otp_token_id = talloc_strdup(kr, chl->tokeninfo[i]->token_id);
+ }
+ if (chl->tokeninfo[0]->challenge != NULL) {
+ kr->otp_challenge = talloc_strdup(kr, chl->tokeninfo[i]->challenge);
+ }
+ /* Allocation errors are ignored on purpose */
+
+ DEBUG(SSSDBG_TRACE_INTERNAL, "Exit answer_otp during pre-auth.\n");
+ return EAGAIN;
+ }
/* Find the first supported tokeninfo which matches our authtoken. */
for (i = 0; chl->tokeninfo[i] != NULL; i++) {
- ret = tokeninfo_matches(kr, chl->tokeninfo[i], pwd, len, &token, &pin);
+ ret = tokeninfo_matches(kr, chl->tokeninfo[i], kr->pd->authtok,
+ &token, &pin);
if (ret == EOK) {
break;
}
@@ -715,6 +859,56 @@ static errno_t pack_response_packet(TALLOC_CTX *mem_ctx, errno_t error,
return EOK;
}
+static errno_t k5c_attach_otp_info_msg(struct krb5_req *kr)
+{
+ uint8_t *msg = NULL;
+ size_t msg_len;
+ int ret;
+ size_t vendor_len = 0;
+ size_t token_id_len = 0;
+ size_t challenge_len = 0;
+
+ msg_len = 3;
+ if (kr->otp_vendor != NULL) {
+ vendor_len = strlen(kr->otp_vendor);
+ msg_len += vendor_len;
+ }
+
+ if (kr->otp_token_id != NULL) {
+ token_id_len = strlen(kr->otp_token_id);
+ msg_len += token_id_len;
+ }
+
+ if (kr->otp_challenge != NULL) {
+ challenge_len = strlen(kr->otp_challenge);
+ msg_len += challenge_len;
+ }
+
+ msg = talloc_zero_size(kr, msg_len);
+ if (msg == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "talloc_size failed.\n");
+ return ENOMEM;
+ }
+
+ if (kr->otp_vendor != NULL) {
+ memcpy(msg, kr->otp_vendor, vendor_len);
+ }
+
+ if (kr->otp_token_id != NULL) {
+ memcpy(msg + vendor_len + 1, kr->otp_token_id, token_id_len);
+ }
+
+ if (kr->otp_challenge != NULL) {
+ memcpy(msg + vendor_len + token_id_len + 2 , kr->otp_challenge,
+ challenge_len);
+ }
+
+ ret = pam_add_response(kr->pd, SSS_PAM_OTP_INFO, msg_len, msg);
+ talloc_zfree(msg);
+
+ return ret;
+}
+
static errno_t k5c_attach_ccname_msg(struct krb5_req *kr)
{
char *msg = NULL;
@@ -1024,12 +1218,20 @@ static krb5_error_code get_and_save_tgt(struct krb5_req *kr,
DEBUG(SSSDBG_TRACE_FUNC,
"Attempting kinit for realm [%s]\n",realm_name);
kerr = krb5_get_init_creds_password(kr->ctx, kr->creds, kr->princ,
- discard_const(password),
- sss_krb5_prompter, kr, 0,
- NULL, kr->options);
- if (kerr != 0) {
- KRB5_CHILD_DEBUG(SSSDBG_CRIT_FAILURE, kerr);
- return kerr;
+ discard_const(password), sss_krb5_prompter, kr, 0,
+ NULL, kr->options);
+ if (kr->pd->cmd == SSS_PAM_PREAUTH) {
+ /* Any errors are ignored during pre-auth, only data is collected to
+ * be send back to the client.*/
+ DEBUG(SSSDBG_TRACE_FUNC,
+ "krb5_get_init_creds_password returned [%d} during pre-auth.\n",
+ kerr);
+ return 0;
+ } else {
+ if (kerr != 0) {
+ KRB5_CHILD_DEBUG(SSSDBG_CRIT_FAILURE, kerr);
+ return kerr;
+ }
}
if (kr->validate) {
@@ -1330,27 +1532,38 @@ static errno_t tgt_req_child(struct krb5_req *kr)
DEBUG(SSSDBG_TRACE_LIBS, "Attempting to get a TGT\n");
- ret = sss_authtok_get_password(kr->pd->authtok, &password, NULL);
- switch (ret) {
- case EOK:
- break;
+ /* No password is needed for pre-auth, or if we have 2FA */
+ if (kr->pd->cmd != SSS_PAM_PREAUTH
+ && sss_authtok_get_type(kr->pd->authtok) != SSS_AUTHTOK_TYPE_2FA) {
+ ret = sss_authtok_get_password(kr->pd->authtok, &password, NULL);
+ switch (ret) {
+ case EOK:
+ break;
- case EACCES:
- DEBUG(SSSDBG_OP_FAILURE, "Invalid authtok type\n");
- return ERR_INVALID_CRED_TYPE;
- break;
+ case EACCES:
+ DEBUG(SSSDBG_OP_FAILURE, "Invalid authtok type\n");
+ return ERR_INVALID_CRED_TYPE;
+ break;
- default:
- DEBUG(SSSDBG_OP_FAILURE, "No credentials available\n");
- return ERR_NO_CREDS;
- break;
+ default:
+ DEBUG(SSSDBG_OP_FAILURE, "No credentials available\n");
+ return ERR_NO_CREDS;
+ break;
+ }
}
kerr = get_and_save_tgt(kr, password);
if (kerr != KRB5KDC_ERR_KEY_EXP) {
- if (kerr == 0) {
- kerr = k5c_attach_ccname_msg(kr);
+ if (kr->pd->cmd == SSS_PAM_PREAUTH) {
+ /* add OTP tokeninfo messge if available */
+ if (kr->otp) {
+ kerr = k5c_attach_otp_info_msg(kr);
+ }
+ } else {
+ if (kerr == 0) {
+ kerr = k5c_attach_ccname_msg(kr);
+ }
}
ret = map_krb5_error(kerr);
goto done;
@@ -1551,6 +1764,10 @@ static errno_t unpack_authtok(struct sss_auth_token *tok,
case SSS_AUTHTOK_TYPE_CCFILE:
ret = sss_authtok_set_ccfile(tok, (char *)(buf + *p), 0);
break;
+ case SSS_AUTHTOK_TYPE_2FA:
+ ret = sss_authtok_set(tok, SSS_AUTHTOK_TYPE_2FA, (buf + *p),
+ auth_token_length);
+ break;
default:
return EINVAL;
}
@@ -2314,11 +2531,13 @@ static krb5_error_code privileged_krb5_setup(struct krb5_req *kr,
}
/* For ccache types FILE: and DIR: we might need to create some directory
- * components as root */
- ret = k5c_ccache_setup(kr, offline);
- if (ret != EOK) {
- DEBUG(SSSDBG_CRIT_FAILURE, "k5c_ccache_setup failed.\n");
- return ret;
+ * components as root. Cache files are not needed during preauth. */
+ if (kr->pd->cmd != SSS_PAM_PREAUTH) {
+ ret = k5c_ccache_setup(kr, offline);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE, "k5c_ccache_setup failed.\n");
+ return ret;
+ }
}
if (!(offline ||
@@ -2493,6 +2712,10 @@ int main(int argc, const char *argv[])
DEBUG(SSSDBG_TRACE_FUNC, "Will perform ticket renewal\n");
ret = renew_tgt_child(kr);
break;
+ case SSS_PAM_PREAUTH:
+ DEBUG(SSSDBG_TRACE_FUNC, "Will perform pre-auth\n");
+ ret = tgt_req_child(kr);
+ break;
default:
DEBUG(SSSDBG_CRIT_FAILURE,
"PAM command [%d] not supported.\n", kr->pd->cmd);
diff --git a/src/providers/krb5/krb5_child_handler.c b/src/providers/krb5/krb5_child_handler.c
index 28ed0cb1c3daf54b629d0b88fb35d50f72aa0f39..423084e9b1b8e2102e2cdf95df52cd835f15b5cd 100644
--- a/src/providers/krb5/krb5_child_handler.c
+++ b/src/providers/krb5/krb5_child_handler.c
@@ -77,6 +77,10 @@ static errno_t pack_authtok(struct io_buffer *buf, size_t *rp,
ret = sss_authtok_get_ccfile(tok, &data, &len);
auth_token_length = len + 1;
break;
+ case SSS_AUTHTOK_TYPE_2FA:
+ data = (char *) sss_authtok_get_data(tok);
+ auth_token_length = sss_authtok_get_size(tok);
+ break;
default:
ret = EINVAL;
}
diff --git a/src/sss_client/sss_cli.h b/src/sss_client/sss_cli.h
index 2895659b9c3ed4ab520ca90846379c22fd9567f7..1d7e8549cd548b00eeedba95080f346439afc3dd 100644
--- a/src/sss_client/sss_cli.h
+++ b/src/sss_client/sss_cli.h
@@ -402,6 +402,12 @@ enum response_type {
* the user.This should only be used in the case where
* it is not possile to use SSS_PAM_USER_INFO.
* @param A zero terminated string. */
+ SSS_PAM_OTP_INFO, /**< A message which optionally may contain the name
+ * of the vendor, the ID of an OTP token and a
+ * challenge.
+ * @param Three zero terminated strings, if one of the
+ * strings is missing the message will contain only
+ * an empty string (\0) for that component. */
SSS_OTP, /**< Indicates that the autotok was a OTP, so don't
* cache it. There is no message.
* @param None. */
--
2.1.0
-------------- next part --------------
From c410b69381540c3db3c2f5f0396b6b8162bca764 Mon Sep 17 00:00:00 2001
From: Sumit Bose <sbose at redhat.com>
Date: Tue, 24 Mar 2015 11:19:46 +0100
Subject: [PATCH 06/14] IPA: create preauth indicator file at startup
---
src/providers/ipa/ipa_init.c | 66 ++++++++++++++++++++++++++++++++++++++++++++
src/sss_client/sss_cli.h | 2 ++
2 files changed, 68 insertions(+)
diff --git a/src/providers/ipa/ipa_init.c b/src/providers/ipa/ipa_init.c
index 4b26e8baad4d0592729aec9a0b188ae89973fa98..1560da1e8a89c67453e79243acc378abdd30d565 100644
--- a/src/providers/ipa/ipa_init.c
+++ b/src/providers/ipa/ipa_init.c
@@ -371,6 +371,62 @@ done:
return ret;
}
+void cleanup_ipa_preauth_indicator(void)
+{
+ int ret;
+
+ ret = unlink(PAM_PREAUTH_INDICATOR);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "Failed to remove preauth indicator file [%s].\n",
+ PAM_PREAUTH_INDICATOR);
+ }
+}
+
+static errno_t create_ipa_preauth_indicator(void)
+{
+ int ret;
+ TALLOC_CTX *tmp_ctx = NULL;
+ int fd;
+
+ tmp_ctx = talloc_new(NULL);
+ if (tmp_ctx == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "talloc_new failed.\n");
+ return ENOMEM;
+ }
+
+ fd = open(PAM_PREAUTH_INDICATOR, O_CREAT | O_EXCL | O_WRONLY | O_NOFOLLOW,
+ 0644);
+ if (fd < 0) {
+ if (errno != EEXIST) {
+ DEBUG(SSSDBG_OP_FAILURE,
+ "Failed to create preauth indicator file [%s].\n",
+ PAM_PREAUTH_INDICATOR);
+ ret = EOK;
+ goto done;
+ }
+
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Preauth indicator file [%s] already exists. " \
+ "Maybe it is left after an unplanned exit. Continuing.\n",
+ PAM_PREAUTH_INDICATOR);
+ } else {
+ close(fd);
+ }
+
+ ret = atexit(cleanup_ipa_preauth_indicator);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_OP_FAILURE, "atexit failed. Continuing.\n");
+ }
+
+ ret = EOK;
+
+done:
+ talloc_free(tmp_ctx);
+
+ return ret;
+}
+
int sssm_ipa_auth_init(struct be_ctx *bectx,
struct bet_ops **ops,
void **pvt_data)
@@ -469,6 +525,16 @@ int sssm_ipa_auth_init(struct be_ctx *bectx,
goto done;
}
+ ret = create_ipa_preauth_indicator();
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CRIT_FAILURE,
+ "Failed to create preauth indicator file, special password " \
+ "prompting might not be available.\n");
+ sss_log(SSSDBG_CRIT_FAILURE,
+ "Failed to create preauth indicator file, special password " \
+ "prompting might not be available.\n");
+ }
+
*ops = &ipa_auth_ops;
*pvt_data = ipa_auth_ctx;
ret = EOK;
diff --git a/src/sss_client/sss_cli.h b/src/sss_client/sss_cli.h
index 1d7e8549cd548b00eeedba95080f346439afc3dd..317700ef8cfcbb1b58e2a7d1ffcc7f00658fe815 100644
--- a/src/sss_client/sss_cli.h
+++ b/src/sss_client/sss_cli.h
@@ -317,6 +317,8 @@ enum sss_authtok_type {
#define SSS_START_OF_PAM_REQUEST 0x4d415049
#define SSS_END_OF_PAM_REQUEST 0x4950414d
+#define PAM_PREAUTH_INDICATOR PUBCONF_PATH"/pam_preauth_available"
+
enum pam_item_type {
SSS_PAM_ITEM_EMPTY = 0x0000,
SSS_PAM_ITEM_USER,
--
2.1.0
-------------- next part --------------
From c8e85195377cb4878a1c879d28ec2b217cb01a38 Mon Sep 17 00:00:00 2001
From: Sumit Bose <sbose at redhat.com>
Date: Thu, 12 Feb 2015 23:08:12 +0100
Subject: [PATCH 07/14] pam_sss: add pre-auth and 2fa support
---
Makefile.am | 1 +
src/sss_client/pam_sss.c | 226 ++++++++++++++++++++++++++++++++++++++++++++++-
2 files changed, 225 insertions(+), 2 deletions(-)
diff --git a/Makefile.am b/Makefile.am
index 5f400585141f8ebf11b51171ffc0f60070261b42..be49a5c6dac8a138aca1b18ea4b16294ac97a003 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -2430,6 +2430,7 @@ pam_sss_la_SOURCES = \
src/sss_client/common.c \
src/sss_client/sss_cli.h \
src/util/atomic_io.c \
+ src/util/authtok-utils.c \
src/sss_client/sss_pam_macros.h \
src/sss_client/sss_pam_compat.h
diff --git a/src/sss_client/pam_sss.c b/src/sss_client/pam_sss.c
index 83769a15337f7ba027f66a8f7e530daa4a287383..e6cf35a90f958e2eceeb737a35e4ebfae99f73fc 100644
--- a/src/sss_client/pam_sss.c
+++ b/src/sss_client/pam_sss.c
@@ -51,6 +51,7 @@
#define FLAGS_USE_AUTHTOK (1 << 2)
#define FLAGS_IGNORE_UNKNOWN_USER (1 << 3)
#define FLAGS_IGNORE_AUTHINFO_UNAVAIL (1 << 4)
+#define FLAGS_USE_2FA (1 << 5)
#define PWEXP_FLAG "pam_sss:password_expired_flag"
#define FD_DESTRUCTOR "pam_sss:fd_destructor"
@@ -88,6 +89,10 @@ struct pam_items {
char *domain_name;
const char *requested_domains;
size_t requested_domains_size;
+ char *otp_vendor;
+ char *otp_token_id;
+ char *otp_challenge;
+ char *first_factor;
};
#define DEBUG_MGS_LEN 1024
@@ -223,6 +228,12 @@ static void overwrite_and_free_authtoks(struct pam_items *pi)
pi->pam_newauthtok = NULL;
}
+ if (pi->first_factor != NULL) {
+ _pam_overwrite_n((void *)pi->first_factor, strlen(pi->first_factor));
+ free((void *)pi->first_factor);
+ pi->first_factor = NULL;
+ }
+
pi->pamstack_authtok = NULL;
pi->pamstack_oldauthtok = NULL;
}
@@ -233,6 +244,15 @@ static void overwrite_and_free_pam_items(struct pam_items *pi)
free(pi->domain_name);
pi->domain_name = NULL;
+
+ free(pi->otp_vendor);
+ pi->otp_vendor = NULL;
+
+ free(pi->otp_token_id);
+ pi->otp_token_id = NULL;
+
+ free(pi->otp_challenge);
+ pi->otp_challenge = NULL;
}
static int pack_message_v3(struct pam_items *pi, size_t *size,
@@ -968,6 +988,7 @@ static int eval_response(pam_handle_t *pamh, size_t buflen, uint8_t *buf,
int32_t type;
int32_t len;
int32_t pam_status;
+ size_t offset;
if (buflen < (2*sizeof(int32_t))) {
D(("response buffer is too small"));
@@ -1074,6 +1095,41 @@ static int eval_response(pam_handle_t *pamh, size_t buflen, uint8_t *buf,
pam_strerror(pamh,ret)));
}
break;
+ case SSS_PAM_OTP_INFO:
+ if (buf[p + (len -1)] != '\0') {
+ D(("system info does not end with \\0."));
+ break;
+ }
+
+ pi->otp_vendor = strdup((char *) &buf[p]);
+ if (pi->otp_vendor == NULL) {
+ D(("strdup failed"));
+ break;
+ }
+
+ offset = strlen(pi->otp_vendor) + 1;
+ if (offset >= len) {
+ D(("OTP message size mismatch"));
+ break;
+ }
+ pi->otp_token_id = strdup((char *) &buf[p + offset]);
+ if (pi->otp_token_id == NULL) {
+ D(("strdup failed"));
+ break;
+ }
+
+ offset += strlen(pi->otp_token_id) + 1;
+ if (offset >= len) {
+ D(("OTP message size mismatch"));
+ break;
+ }
+ pi->otp_challenge = strdup((char *) &buf[p + offset]);
+ if (pi->otp_challenge == NULL) {
+ D(("strdup failed"));
+ break;
+ }
+
+ break;
default:
D(("Unknown response type [%d]", type));
}
@@ -1095,6 +1151,7 @@ static int get_pam_items(pam_handle_t *pamh, struct pam_items *pi)
pi->pam_newauthtok_type = SSS_AUTHTOK_TYPE_EMPTY;
pi->pam_newauthtok = NULL;
pi->pam_newauthtok_size = 0;
+ pi->first_factor = NULL;
ret = pam_get_item(pamh, PAM_SERVICE, (const void **) &(pi->pam_service));
if (ret != PAM_SUCCESS) return ret;
@@ -1149,6 +1206,10 @@ static int get_pam_items(pam_handle_t *pamh, struct pam_items *pi)
if (pi->requested_domains == NULL) pi->requested_domains = "";
pi->requested_domains_size = strlen(pi->requested_domains) + 1;
+ pi->otp_vendor = NULL;
+ pi->otp_token_id = NULL;
+ pi->otp_challenge = NULL;
+
return PAM_SUCCESS;
}
@@ -1280,6 +1341,7 @@ static int send_and_receive(pam_handle_t *pamh, struct pam_items *pi,
case SSS_PAM_OPEN_SESSION:
case SSS_PAM_SETCRED:
case SSS_PAM_CLOSE_SESSION:
+ case SSS_PAM_PREAUTH:
break;
default:
D(("Illegal task [%d]", task));
@@ -1327,6 +1389,133 @@ static int prompt_password(pam_handle_t *pamh, struct pam_items *pi,
return PAM_SUCCESS;
}
+static int prompt_2fa(pam_handle_t *pamh, struct pam_items *pi,
+ const char *prompt_fa1, const char *prompt_fa2)
+{
+ int ret;
+ struct pam_conv *conv;
+ const struct pam_message *mesg[2] = {NULL, NULL};
+ struct pam_message *m1;
+ struct pam_message *m2;
+ struct pam_response *resp = NULL;
+ size_t needed_size;
+
+ ret = pam_get_item(pamh, PAM_CONV, (const void **) &conv);
+ if (ret != PAM_SUCCESS) {
+ return ret;
+ }
+
+ m1 = malloc(sizeof(struct pam_message));
+ if (m1 == NULL) {
+ D(("Malloc failed."));
+ return PAM_SYSTEM_ERR;
+ }
+
+ m2 = malloc(sizeof(struct pam_message));
+ if (m2 == NULL) {
+ D(("Malloc failed."));
+ free(m1);
+ return PAM_SYSTEM_ERR;
+ }
+ m1->msg_style = PAM_PROMPT_ECHO_OFF;
+ m1->msg = prompt_fa1;
+ m2->msg_style = PAM_PROMPT_ECHO_OFF;
+ m2->msg = prompt_fa2;
+
+ mesg[0] = (const struct pam_message *) m1;
+ mesg[1] = (const struct pam_message *) m2;
+
+ ret=conv->conv(2, mesg, &resp, conv->appdata_ptr);
+ free(m1);
+ free(m2);
+ if (ret != PAM_SUCCESS) {
+ D(("Conversation failure: %s.", pam_strerror(pamh,ret)));
+ return ret;
+ }
+
+ if (resp == NULL) {
+ D(("response expected, but resp==NULL"));
+ return PAM_SYSTEM_ERR;
+ }
+
+ if (resp[0].resp == NULL || *(resp[0].resp) == '\0') {
+ D(("Missing factor."));
+ ret = PAM_CRED_INSUFFICIENT;
+ goto done;
+ }
+
+ if (resp[1].resp == NULL || *(resp[1].resp) == '\0'
+ || (pi->pam_service != NULL && strcmp(pi->pam_service, "sshd") == 0
+ && strcmp(resp[0].resp, resp[1].resp) == 0)) {
+ /* Missing second factor, assume first factor contains combined 2FA
+ * credentials.
+ * Special handling for SSH with password authentication. Combined
+ * 2FA credentials are used but SSH puts them in both responses. */
+
+ pi->pam_authtok = strndup(resp[0].resp, MAX_AUTHTOK_SIZE);
+ if (pi->pam_authtok == NULL) {
+ D(("strndup failed."));
+ ret = PAM_BUF_ERR;
+ goto done;
+ }
+ pi->pam_authtok_size = strlen(pi->pam_authtok) + 1;
+ pi->pam_authtok_type = SSS_AUTHTOK_TYPE_PASSWORD;
+ } else {
+
+ ret = sss_auth_pack_2fa_blob(resp[0].resp, 0, resp[1].resp, 0, NULL, 0,
+ &needed_size);
+ if (ret != EAGAIN) {
+ D(("sss_auth_pack_2fa_blob failed."));
+ ret = PAM_BUF_ERR;
+ goto done;
+ }
+
+ pi->pam_authtok = malloc(needed_size);
+ if (pi->pam_authtok == NULL) {
+ D(("malloc failed."));
+ ret = PAM_BUF_ERR;
+ goto done;
+ }
+
+ ret = sss_auth_pack_2fa_blob(resp[0].resp, 0, resp[1].resp, 0,
+ (uint8_t* ) pi->pam_authtok, needed_size,
+ &needed_size);
+ if (ret != EOK) {
+ D(("sss_auth_pack_2fa_blob failed."));
+ ret = PAM_BUF_ERR;
+ goto done;
+ }
+
+ pi->pam_authtok_size = needed_size;
+ pi->pam_authtok_type = SSS_AUTHTOK_TYPE_2FA;
+ pi->first_factor = strndup(resp[0].resp, MAX_AUTHTOK_SIZE);
+ if (pi->first_factor == NULL) {
+ D(("strndup failed."));
+ ret = PAM_BUF_ERR;
+ goto done;
+ }
+ }
+
+ ret = PAM_SUCCESS;
+
+done:
+ if (resp != NULL) {
+ if (resp[0].resp != NULL) {
+ _pam_overwrite((void *)resp[0].resp);
+ free(resp[0].resp);
+ }
+ if (resp[1].resp != NULL) {
+ _pam_overwrite((void *)resp[1].resp);
+ free(resp[1].resp);
+ }
+
+ free(resp);
+ resp = NULL;
+ }
+
+ return ret;
+}
+
static int prompt_new_password(pam_handle_t *pamh, struct pam_items *pi)
{
int ret;
@@ -1410,6 +1599,8 @@ static void eval_argv(pam_handle_t *pamh, int argc, const char **argv,
*flags |= FLAGS_IGNORE_UNKNOWN_USER;
} else if (strcmp(*argv, "ignore_authinfo_unavail") == 0) {
*flags |= FLAGS_IGNORE_AUTHINFO_UNAVAIL;
+ } else if (strcmp(*argv, "use_2fa") == 0) {
+ *flags |= FLAGS_USE_2FA;
} else {
logger(pamh, LOG_WARNING, "unknown option: %s", *argv);
}
@@ -1433,14 +1624,28 @@ static int get_authtok_for_authentication(pam_handle_t *pamh,
}
pi->pam_authtok_size = strlen(pi->pam_authtok);
} else {
- ret = prompt_password(pamh, pi, _("Password: "));
+ if ( flags & FLAGS_USE_2FA
+ || (pi->otp_vendor != NULL && pi->otp_token_id != NULL
+ && pi->otp_challenge != NULL)) {
+ ret = prompt_2fa(pamh, pi, _("First Factor: "),
+ _("Second Factor: "));
+ } else {
+ ret = prompt_password(pamh, pi, _("Password: "));
+ }
if (ret != PAM_SUCCESS) {
D(("failed to get password from user"));
return ret;
}
if (flags & FLAGS_FORWARD_PASS) {
- ret = pam_set_item(pamh, PAM_AUTHTOK, pi->pam_authtok);
+ if (pi->pam_authtok_type == SSS_AUTHTOK_TYPE_PASSWORD) {
+ ret = pam_set_item(pamh, PAM_AUTHTOK, pi->pam_authtok);
+ } else if (pi->pam_authtok_type == SSS_AUTHTOK_TYPE_2FA
+ && pi->first_factor != NULL) {
+ ret = pam_set_item(pamh, PAM_AUTHTOK, pi->first_factor);
+ } else {
+ ret = EINVAL;
+ }
if (ret != PAM_SUCCESS) {
D(("Failed to set PAM_AUTHTOK [%s], "
"authtok may not be available for other modules",
@@ -1576,6 +1781,23 @@ static int pam_sss(enum sss_cli_command task, pam_handle_t *pamh,
switch(task) {
case SSS_PAM_AUTHENTICATE:
+ /* Only do preauth if
+ * - FLAGS_USE_FIRST_PASS is not set
+ * - no password is on the stack
+ * - preauth indicator file exists. */
+ if ( !(flags & FLAGS_USE_FIRST_PASS) && pi.pam_authtok == NULL
+ && access(PAM_PREAUTH_INDICATOR, F_OK) == 0) {
+ pam_status = send_and_receive(pamh, &pi, SSS_PAM_PREAUTH,
+ quiet_mode);
+ if (pam_status != PAM_SUCCESS) {
+ D(("send_and_receive returned [%d] during pre-auth",
+ pam_status));
+ /* Since we are only interested in the result message and
+ * will always use password authentication as a fallback,
+ * errors can be ignored here. */
+ }
+ }
+
ret = get_authtok_for_authentication(pamh, &pi, flags);
if (ret != PAM_SUCCESS) {
D(("failed to get authentication token: %s",
--
2.1.0
-------------- next part --------------
From 6282f77f1d00a46bee3cfd1890403f5469ed814f Mon Sep 17 00:00:00 2001
From: Sumit Bose <sbose at redhat.com>
Date: Tue, 24 Mar 2015 13:00:14 +0100
Subject: [PATCH 08/14] Add cache_credentials_minimal_first_factor_length
config option
---
src/confdb/confdb.c | 11 +++++++++++
src/confdb/confdb.h | 4 ++++
src/config/SSSDConfigTest.py | 2 ++
src/config/etc/sssd.api.conf | 1 +
src/man/sssd.conf.5.xml | 22 ++++++++++++++++++++++
src/util/domain_info_utils.c | 2 ++
6 files changed, 42 insertions(+)
diff --git a/src/confdb/confdb.c b/src/confdb/confdb.c
index 5aee1ee069bf4ed9ac968588518a3350f573132b..cb458dfc2c8f14bf397ae70e8b829fbbbc2ddff2 100644
--- a/src/confdb/confdb.c
+++ b/src/confdb/confdb.c
@@ -985,6 +985,17 @@ static int confdb_get_domain_internal(struct confdb_ctx *cdb,
goto done;
}
+ ret = get_entry_as_uint32(res->msgs[0],
+ &domain->cache_credentials_min_ff_length,
+ CONFDB_DOMAIN_CACHE_CREDS_MIN_FF_LENGTH,
+ CONFDB_DEFAULT_CACHE_CREDS_MIN_FF_LENGTH);
+ if(ret != EOK) {
+ DEBUG(SSSDBG_FATAL_FAILURE,
+ "Invalid value for %s\n",
+ CONFDB_DOMAIN_CACHE_CREDS_MIN_FF_LENGTH);
+ goto done;
+ }
+
ret = get_entry_as_bool(res->msgs[0], &domain->legacy_passwords,
CONFDB_DOMAIN_LEGACY_PASS, 0);
if(ret != EOK) {
diff --git a/src/confdb/confdb.h b/src/confdb/confdb.h
index 19c56402069f9a7001188e91f77db8ad8525d690..c8c91288cd63df7629a98802b7b5373df92d6ca4 100644
--- a/src/confdb/confdb.h
+++ b/src/confdb/confdb.h
@@ -162,6 +162,9 @@
#define CONFDB_DOMAIN_MINID "min_id"
#define CONFDB_DOMAIN_MAXID "max_id"
#define CONFDB_DOMAIN_CACHE_CREDS "cache_credentials"
+#define CONFDB_DOMAIN_CACHE_CREDS_MIN_FF_LENGTH \
+ "cache_credentials_minimal_first_factor_length"
+#define CONFDB_DEFAULT_CACHE_CREDS_MIN_FF_LENGTH 8
#define CONFDB_DOMAIN_LEGACY_PASS "store_legacy_passwords"
#define CONFDB_DOMAIN_MPG "magic_private_groups"
#define CONFDB_DOMAIN_FQ "use_fully_qualified_names"
@@ -221,6 +224,7 @@ struct sss_domain_info {
uint32_t id_max;
bool cache_credentials;
+ uint32_t cache_credentials_min_ff_length;
bool legacy_passwords;
bool case_sensitive;
bool case_preserve;
diff --git a/src/config/SSSDConfigTest.py b/src/config/SSSDConfigTest.py
index 3a5312ea945b5247c69e97b73565b7061e037b69..db16bc433cf4c47c6a15760d85b322a6655aa0c1 100755
--- a/src/config/SSSDConfigTest.py
+++ b/src/config/SSSDConfigTest.py
@@ -494,6 +494,7 @@ class SSSDConfigTestSSSDDomain(unittest.TestCase):
'command',
'enumerate',
'cache_credentials',
+ 'cache_credentials_minimal_first_factor_length',
'store_legacy_passwords',
'use_fully_qualified_names',
'ignore_group_members',
@@ -853,6 +854,7 @@ class SSSDConfigTestSSSDDomain(unittest.TestCase):
'command',
'enumerate',
'cache_credentials',
+ 'cache_credentials_minimal_first_factor_length',
'store_legacy_passwords',
'use_fully_qualified_names',
'ignore_group_members',
diff --git a/src/config/etc/sssd.api.conf b/src/config/etc/sssd.api.conf
index 4fa542704fbd3af065843e777b84b6305ec3e78b..f1ac6366c73c03fe5c60c79bfe7b15fde9382215 100644
--- a/src/config/etc/sssd.api.conf
+++ b/src/config/etc/sssd.api.conf
@@ -110,6 +110,7 @@ subdomain_enumerate = str, None, false
force_timeout = int, None, false
offline_timeout = int, None, false
cache_credentials = bool, None, false
+cache_credentials_minimal_first_factor_length = int, None, false
store_legacy_passwords = bool, None, false
use_fully_qualified_names = bool, None, false
ignore_group_members = bool, None, false
diff --git a/src/man/sssd.conf.5.xml b/src/man/sssd.conf.5.xml
index 92c64b9fd337377ce301af63bbd146118328cd6d..fc34d8ee246a75099b4dfcfd58ed480a4ef12318 100644
--- a/src/man/sssd.conf.5.xml
+++ b/src/man/sssd.conf.5.xml
@@ -1392,6 +1392,28 @@ pam_account_expired_message = Account expired, please call help desk.
</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term>cache_credentials_minimal_first_factor_length (int)</term>
+ <listitem>
+ <para>
+ If 2-Factor-Authentication (2FA) is used and
+ credentials should be saved this value determines
+ the minimal lenght the first authentication factor
+ (long term password) must have to be saved as SHA512
+ hash into the cache.
+ </para>
+ <para>
+ This should avoid that the short PINs of a PIN based
+ 2FA scheme are saved in the cache which would make
+ them easy targets for brute-force attacks.
+ </para>
+ <para>
+ Default: 8
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term>account_cache_expiration (integer)</term>
<listitem>
diff --git a/src/util/domain_info_utils.c b/src/util/domain_info_utils.c
index a889bd7aad3a8435d1177cb44371df8d527160c0..dfa322f4b42347d6fcd88c724d8ac0f6994c840d 100644
--- a/src/util/domain_info_utils.c
+++ b/src/util/domain_info_utils.c
@@ -288,6 +288,8 @@ struct sss_domain_info *new_subdomain(TALLOC_CTX *mem_ctx,
dom->id_max = parent->id_max ? parent->id_max : 0xffffffff;
dom->pwd_expiration_warning = parent->pwd_expiration_warning;
dom->cache_credentials = parent->cache_credentials;
+ dom->cache_credentials_min_ff_length =
+ parent->cache_credentials_min_ff_length;
dom->case_sensitive = false;
dom->user_timeout = parent->user_timeout;
dom->group_timeout = parent->group_timeout;
--
2.1.0
-------------- next part --------------
From 5ef4f44bd2feb221dbe70c3748fa09e522ac98a0 Mon Sep 17 00:00:00 2001
From: Sumit Bose <sbose at redhat.com>
Date: Tue, 24 Mar 2015 15:35:01 +0100
Subject: [PATCH 09/14] sysdb: add sysdb_cache_password_ex()
---
src/db/sysdb.h | 9 +++++++++
src/db/sysdb_ops.c | 25 ++++++++++++++++++++---
src/tests/sysdb-tests.c | 53 +++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 84 insertions(+), 3 deletions(-)
diff --git a/src/db/sysdb.h b/src/db/sysdb.h
index 2a3a2df98fa1e51cf024b7bd57347e8b02449eff..c3d2c1406321e01b325e04e0e3f263893eb91b2e 100644
--- a/src/db/sysdb.h
+++ b/src/db/sysdb.h
@@ -24,6 +24,7 @@
#include "util/util.h"
#include "confdb/confdb.h"
+#include "sss_client/sss_cli.h"
#include <tevent.h>
#define CACHE_SYSDB_FILE "cache_%s.ldb"
@@ -105,6 +106,8 @@
#define SYSDB_SERVERHOSTNAME "serverHostname"
#define SYSDB_CACHEDPWD "cachedPassword"
+#define SYSDB_CACHEDPWD_TYPE "cachedPasswordType"
+#define SYSDB_CACHEDPWD_FA2_LEN "cachedPasswordSecondFactorLen"
#define SYSDB_UUID "uniqueID"
#define SYSDB_SID "objectSID"
@@ -888,6 +891,12 @@ int sysdb_cache_password(struct sss_domain_info *domain,
const char *username,
const char *password);
+int sysdb_cache_password_ex(struct sss_domain_info *domain,
+ const char *username,
+ const char *password,
+ enum sss_authtok_type authtok_type,
+ size_t second_factor_size);
+
errno_t check_failed_login_attempts(struct confdb_ctx *cdb,
struct ldb_message *ldb_msg,
uint32_t *failed_login_attempts,
diff --git a/src/db/sysdb_ops.c b/src/db/sysdb_ops.c
index 1f84a60ba332d70529b2170c04415d7fc0704597..b4b2eb9fc2ba6055fcfc770601c04815464228cd 100644
--- a/src/db/sysdb_ops.c
+++ b/src/db/sysdb_ops.c
@@ -2234,9 +2234,11 @@ int sysdb_remove_group_member(struct sss_domain_info *domain,
/* =Password-Caching====================================================== */
-int sysdb_cache_password(struct sss_domain_info *domain,
- const char *username,
- const char *password)
+int sysdb_cache_password_ex(struct sss_domain_info *domain,
+ const char *username,
+ const char *password,
+ enum sss_authtok_type authtok_type,
+ size_t second_factor_len)
{
TALLOC_CTX *tmp_ctx;
struct sysdb_attrs *attrs;
@@ -2269,6 +2271,15 @@ int sysdb_cache_password(struct sss_domain_info *domain,
ret = sysdb_attrs_add_string(attrs, SYSDB_CACHEDPWD, hash);
if (ret) goto fail;
+ ret = sysdb_attrs_add_long(attrs, SYSDB_CACHEDPWD_TYPE, authtok_type);
+ if (ret) goto fail;
+
+ if (authtok_type == SSS_AUTHTOK_TYPE_2FA && second_factor_len > 0) {
+ ret = sysdb_attrs_add_long(attrs, SYSDB_CACHEDPWD_FA2_LEN,
+ second_factor_len);
+ if (ret) goto fail;
+ }
+
/* FIXME: should we use a different attribute for chache passwords ?? */
ret = sysdb_attrs_add_long(attrs, "lastCachedPasswordChange",
(long)time(NULL));
@@ -2293,6 +2304,14 @@ fail:
return ret;
}
+int sysdb_cache_password(struct sss_domain_info *domain,
+ const char *username,
+ const char *password)
+{
+ return sysdb_cache_password_ex(domain, username, password,
+ SSS_AUTHTOK_TYPE_PASSWORD, 0);
+}
+
/* =Custom Search================== */
int sysdb_search_custom(TALLOC_CTX *mem_ctx,
diff --git a/src/tests/sysdb-tests.c b/src/tests/sysdb-tests.c
index a6c6b33de7be7665e5284524c840de7157ed1891..22b1de428f9918ff82fe64b0ca3941056eac21d0 100644
--- a/src/tests/sysdb-tests.c
+++ b/src/tests/sysdb-tests.c
@@ -1809,6 +1809,57 @@ START_TEST (test_sysdb_cache_password)
}
END_TEST
+START_TEST (test_sysdb_cache_password_ex)
+{
+ struct sysdb_test_ctx *test_ctx;
+ struct test_data *data;
+ int ret;
+ struct ldb_result *res;
+ const char *attrs[] = { SYSDB_CACHEDPWD_TYPE, SYSDB_CACHEDPWD_FA2_LEN,
+ NULL };
+ int val;
+
+ /* Setup */
+ ret = setup_sysdb_tests(&test_ctx);
+ fail_unless(ret == EOK, "Could not set up the test");
+
+ data = talloc_zero(test_ctx, struct test_data);
+ data->ctx = test_ctx;
+ data->ev = test_ctx->ev;
+ data->username = talloc_asprintf(data, "testuser%d", _i);
+
+ ret = sysdb_get_user_attr(test_ctx, test_ctx->domain, data->username,
+ attrs, &res);
+ fail_unless(ret == EOK, "sysdb_get_user_attr request failed [%d].", ret);
+
+ val = ldb_msg_find_attr_as_int(res->msgs[0], SYSDB_CACHEDPWD_TYPE, 0);
+ fail_unless(val == SSS_AUTHTOK_TYPE_PASSWORD,
+ "Unexptected authtok type, found [%d], expected [%d].",
+ val, SSS_AUTHTOK_TYPE_PASSWORD);
+
+ ret = sysdb_cache_password_ex(test_ctx->domain, data->username,
+ data->username, SSS_AUTHTOK_TYPE_2FA, 12);
+
+ fail_unless(ret == EOK, "sysdb_cache_password request failed [%d].", ret);
+
+ ret = sysdb_get_user_attr(test_ctx, test_ctx->domain, data->username,
+ attrs, &res);
+ fail_unless(ret == EOK, "sysdb_get_user_attr request failed [%d].", ret);
+
+ val = ldb_msg_find_attr_as_int(res->msgs[0], SYSDB_CACHEDPWD_TYPE, 0);
+ fail_unless(val == SSS_AUTHTOK_TYPE_2FA,
+ "Unexptected authtok type, found [%d], expected [%d].",
+ val, SSS_AUTHTOK_TYPE_2FA);
+
+ val = ldb_msg_find_attr_as_int(res->msgs[0], SYSDB_CACHEDPWD_FA2_LEN, 0);
+ fail_unless(val == 12,
+ "Unexptected second factor lenght, found [%d], expected [%d].",
+ val, 12);
+
+ talloc_free(test_ctx);
+}
+END_TEST
+
static void cached_authentication_without_expiration(const char *username,
const char *password,
int expected_result)
@@ -6241,6 +6292,8 @@ Suite *create_sysdb_suite(void)
27010, 27011);
tcase_add_loop_test(tc_sysdb, test_sysdb_cached_authentication, 27010, 27011);
+ tcase_add_loop_test(tc_sysdb, test_sysdb_cache_password_ex, 27010, 27011);
+
/* ASQ search test */
tcase_add_loop_test(tc_sysdb, test_sysdb_prepare_asq_test_user, 28011, 28020);
tcase_add_test(tc_sysdb, test_sysdb_asq_search);
--
2.1.0
-------------- next part --------------
From 7384c66931f8ed4cbf7a0a632558198538741553 Mon Sep 17 00:00:00 2001
From: Sumit Bose <sbose at redhat.com>
Date: Tue, 24 Mar 2015 15:53:17 +0100
Subject: [PATCH 10/14] krb5: save hash of the first authentication factor to
the cache
---
src/providers/krb5/krb5_auth.c | 26 +++++++++++++++++++++++---
1 file changed, 23 insertions(+), 3 deletions(-)
diff --git a/src/providers/krb5/krb5_auth.c b/src/providers/krb5/krb5_auth.c
index 651a9201756be8bc80199636b494b2773cff9c3e..162f6a1572238591a06f1e314a42810c8df029b1 100644
--- a/src/providers/krb5/krb5_auth.c
+++ b/src/providers/krb5/krb5_auth.c
@@ -265,6 +265,9 @@ static void krb5_auth_store_creds(struct sss_domain_info *domain,
struct pam_data *pd)
{
const char *password = NULL;
+ const char *fa2;
+ size_t password_len;
+ size_t fa2_len = 0;
int ret = EOK;
switch(pd->cmd) {
@@ -276,7 +279,20 @@ static void krb5_auth_store_creds(struct sss_domain_info *domain,
break;
case SSS_PAM_AUTHENTICATE:
case SSS_PAM_CHAUTHTOK_PRELIM:
- ret = sss_authtok_get_password(pd->authtok, &password, NULL);
+ if (sss_authtok_get_type(pd->authtok) == SSS_AUTHTOK_TYPE_2FA) {
+ ret = sss_authtok_get_2fa(pd->authtok, &password, &password_len,
+ &fa2, &fa2_len);
+ if (ret == EOK && password_len <
+ domain->cache_credentials_min_ff_length) {
+ DEBUG(SSSDBG_FATAL_FAILURE,
+ "First factor is too short to be cache, " \
+ "minimum length is [%u].\n",
+ domain->cache_credentials_min_ff_length);
+ ret = EINVAL;
+ }
+ } else {
+ ret = sss_authtok_get_password(pd->authtok, &password, NULL);
+ }
break;
case SSS_PAM_CHAUTHTOK:
ret = sss_authtok_get_password(pd->newauthtok, &password, NULL);
@@ -302,7 +318,8 @@ static void krb5_auth_store_creds(struct sss_domain_info *domain,
return;
}
- ret = sysdb_cache_password(domain, pd->user, password);
+ ret = sysdb_cache_password_ex(domain, pd->user, password,
+ sss_authtok_get_type(pd->authtok), fa2_len);
if (ret) {
DEBUG(SSSDBG_OP_FAILURE,
"Failed to cache password, offline auth may not work."
@@ -1018,7 +1035,10 @@ static void krb5_auth_done(struct tevent_req *subreq)
goto done;
}
- if (state->be_ctx->domain->cache_credentials == TRUE && !res->otp) {
+ if (state->be_ctx->domain->cache_credentials == TRUE
+ && (!res->otp
+ || (res->otp && sss_authtok_get_type(pd->authtok) ==
+ SSS_AUTHTOK_TYPE_2FA))) {
krb5_auth_store_creds(state->domain, pd);
}
--
2.1.0
-------------- next part --------------
From 5d220353e80c4dcecce175c2182d09b74e52134b Mon Sep 17 00:00:00 2001
From: Sumit Bose <sbose at redhat.com>
Date: Wed, 25 Mar 2015 12:04:57 +0100
Subject: [PATCH 11/14] krb5: try delayed online authentication only for single
factor auth
---
src/providers/krb5/krb5_auth.c | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/src/providers/krb5/krb5_auth.c b/src/providers/krb5/krb5_auth.c
index 162f6a1572238591a06f1e314a42810c8df029b1..7aa11c46515d7560cf73b4e21ffaa66472e9db60 100644
--- a/src/providers/krb5/krb5_auth.c
+++ b/src/providers/krb5/krb5_auth.c
@@ -207,6 +207,13 @@ static void krb5_auth_cache_creds(struct krb5_ctx *krb5_ctx,
const char *password = NULL;
errno_t ret;
+ if (sss_authtok_get_type(pd->authtok) != SSS_AUTHTOK_TYPE_PASSWORD) {
+ DEBUG(SSSDBG_MINOR_FAILURE,
+ "Delayed authentication is only available for password " \
+ "authentication (single factor).\n");
+ return;
+ }
+
ret = sss_authtok_get_password(pd->authtok, &password, NULL);
if (ret != EOK) {
DEBUG(SSSDBG_FATAL_FAILURE,
--
2.1.0
-------------- next part --------------
From 02bbf42ba4926efc39d878d4f75b7435d8f10920 Mon Sep 17 00:00:00 2001
From: Sumit Bose <sbose at redhat.com>
Date: Fri, 27 Mar 2015 15:20:13 +0100
Subject: [PATCH 12/14] 2FA offline auth
---
src/db/sysdb_ops.c | 77 ++++++++++++++++++++++++++++++++++++++++--
src/responder/pam/pamsrv_cmd.c | 35 +++++++++++++++++--
2 files changed, 107 insertions(+), 5 deletions(-)
diff --git a/src/db/sysdb_ops.c b/src/db/sysdb_ops.c
index b4b2eb9fc2ba6055fcfc770601c04815464228cd..d98ef8ddac4ca6f10efba594254fc6faf437fc8e 100644
--- a/src/db/sysdb_ops.c
+++ b/src/db/sysdb_ops.c
@@ -3161,6 +3161,76 @@ done:
return ret;
}
+static errno_t check_for_combined_2fa_password(struct sss_domain_info *domain,
+ struct ldb_message *ldb_msg,
+ const char *password,
+ const char *userhash)
+{
+
+ unsigned int cached_authtok_type;
+ unsigned int cached_fa2_len;
+ char *short_pw;
+ char *comphash;
+ size_t pw_len;
+ TALLOC_CTX *tmp_ctx;
+ int ret;
+
+ cached_authtok_type = ldb_msg_find_attr_as_uint(ldb_msg,
+ SYSDB_CACHEDPWD_TYPE,
+ SSS_AUTHTOK_TYPE_EMPTY);
+ if (cached_authtok_type != SSS_AUTHTOK_TYPE_2FA) {
+ DEBUG(SSSDBG_TRACE_LIBS, "Wrong authtok type.\n");
+ return EINVAL;
+ }
+
+ cached_fa2_len = ldb_msg_find_attr_as_uint(ldb_msg, SYSDB_CACHEDPWD_FA2_LEN,
+ 0);
+ if (cached_fa2_len == 0) {
+ DEBUG(SSSDBG_TRACE_LIBS, "Second factor size not available.\n");
+ return EINVAL;
+ }
+
+ pw_len = strlen(password);
+ if (pw_len < cached_fa2_len + domain->cache_credentials_min_ff_length) {
+ DEBUG(SSSDBG_TRACE_LIBS, "Password too short.\n");
+ return EINVAL;
+ }
+
+ tmp_ctx = talloc_new(NULL);
+ if (tmp_ctx == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "talloc_new failed.\n");
+ return ENOMEM;
+ }
+
+ short_pw = talloc_strndup(tmp_ctx , password, (pw_len - cached_fa2_len));
+ if (short_pw == NULL) {
+ DEBUG(SSSDBG_OP_FAILURE, "talloc_strndup failed.\n");
+ ret = ENOMEM;
+ goto done;
+ }
+
+ ret = s3crypt_sha512(tmp_ctx, short_pw, userhash, &comphash);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_CONF_SETTINGS, "Failed to create password hash.\n");
+ ret = ERR_INTERNAL;
+ goto done;
+ }
+
+ if (strcmp(userhash, comphash) != 0) {
+ DEBUG(SSSDBG_MINOR_FAILURE,
+ "Hash of shorten password does not match.\n");
+ ret = ERR_AUTH_FAILED;
+ goto done;
+ }
+
+ ret = EOK;
+
+done:
+ talloc_free(tmp_ctx);
+
+ return ret;
+}
+
int sysdb_cache_auth(struct sss_domain_info *domain,
const char *name,
const char *password,
@@ -3174,7 +3244,8 @@ int sysdb_cache_auth(struct sss_domain_info *domain,
SYSDB_LAST_LOGIN, SYSDB_LAST_ONLINE_AUTH,
"lastCachedPasswordChange",
"accountExpires", SYSDB_FAILED_LOGIN_ATTEMPTS,
- SYSDB_LAST_FAILED_LOGIN, NULL };
+ SYSDB_LAST_FAILED_LOGIN, SYSDB_CACHEDPWD_TYPE,
+ SYSDB_CACHEDPWD_FA2_LEN, NULL };
struct ldb_message *ldb_msg;
const char *userhash;
char *comphash;
@@ -3285,7 +3356,9 @@ int sysdb_cache_auth(struct sss_domain_info *domain,
goto done;
}
- if (strcmp(userhash, comphash) == 0) {
+ if (strcmp(userhash, comphash) == 0
+ || check_for_combined_2fa_password(domain, ldb_msg,
+ password, userhash) == EOK ) {
/* TODO: probable good point for audit logging */
DEBUG(SSSDBG_CONF_SETTINGS, "Hashes do match!\n");
authentication_successful = true;
diff --git a/src/responder/pam/pamsrv_cmd.c b/src/responder/pam/pamsrv_cmd.c
index f84349d1f60e4e7c858b9bc6303ade3a962172d1..20aeda41e143f10faa9319fea6b2a9ff9041ed55 100644
--- a/src/responder/pam/pamsrv_cmd.c
+++ b/src/responder/pam/pamsrv_cmd.c
@@ -528,6 +528,34 @@ static void pam_reply_delay(struct tevent_context *ev, struct tevent_timer *te,
pam_reply(preq);
}
+static errno_t get_password_for_cache_auth(struct sss_auth_token *authtok,
+ const char **password)
+{
+ int ret;
+ size_t pw_len;
+ const char *fa2;
+ size_t fa2_len;
+
+ switch (sss_authtok_get_type(authtok)) {
+ case SSS_AUTHTOK_TYPE_PASSWORD:
+ ret = sss_authtok_get_password(authtok, password, NULL);
+ break;
+ case SSS_AUTHTOK_TYPE_2FA:
+ ret = sss_authtok_get_2fa(authtok, password, &pw_len, &fa2, &fa2_len);
+ break;
+ default:
+ DEBUG(SSSDBG_FATAL_FAILURE, "Unsupported auth token type [%d].\n",
+ sss_authtok_get_type(authtok));
+ ret = EINVAL;
+ }
+ if (ret != EOK) {
+ DEBUG(SSSDBG_FATAL_FAILURE, "Failed to get password.\n");
+ return ret;
+ }
+
+ return EOK;
+}
+
static int pam_forwarder(struct cli_ctx *cctx, int pam_cmd);
static void pam_handle_cached_login(struct pam_auth_req *preq, int ret,
time_t expire_date, time_t delayed_until);
@@ -587,9 +615,10 @@ static void pam_reply(struct pam_auth_req *preq)
goto done;
}
- ret = sss_authtok_get_password(pd->authtok, &password, NULL);
- if (ret) {
- DEBUG(SSSDBG_FATAL_FAILURE, "Failed to get password.\n");
+ ret = get_password_for_cache_auth(pd->authtok, &password);
+ if (ret != EOK) {
+ DEBUG(SSSDBG_FATAL_FAILURE,
+ "get_password_and_type_for_cache_auth failed.\n");
goto done;
}
--
2.1.0
-------------- next part --------------
From e6798acb2d6aad59b2ef6ace0a8162b6e297716a Mon Sep 17 00:00:00 2001
From: Sumit Bose <sbose at redhat.com>
Date: Wed, 18 Mar 2015 16:02:47 +0100
Subject: [PATCH 13/14] pam_sss: move message encoding into separate file
---
Makefile.am | 2 +
src/sss_client/pam_message.c | 178 +++++++++++++++++++++++++++++++++++++++++++
src/sss_client/pam_message.h | 61 +++++++++++++++
src/sss_client/pam_sss.c | 177 +-----------------------------------------
4 files changed, 242 insertions(+), 176 deletions(-)
create mode 100644 src/sss_client/pam_message.c
create mode 100644 src/sss_client/pam_message.h
diff --git a/Makefile.am b/Makefile.am
index be49a5c6dac8a138aca1b18ea4b16294ac97a003..412b4426af8d07f7f4eb4d6dcaa71ad99e9cee5f 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -614,6 +614,7 @@ dist_noinst_HEADERS = \
src/tests/cmocka/common_mock_sdap.h \
src/tests/cmocka/common_mock_sysdb_objects.h \
src/tests/cmocka/test_expire_common.h \
+ src/sss_client/pam_message.h \
src/sss_client/ssh/sss_ssh_client.h \
src/sss_client/sudo/sss_sudo.h \
src/sss_client/libwbclient/libwbclient.h \
@@ -2427,6 +2428,7 @@ endif
pamlib_LTLIBRARIES = pam_sss.la
pam_sss_la_SOURCES = \
src/sss_client/pam_sss.c \
+ src/sss_client/pam_message.c \
src/sss_client/common.c \
src/sss_client/sss_cli.h \
src/util/atomic_io.c \
diff --git a/src/sss_client/pam_message.c b/src/sss_client/pam_message.c
new file mode 100644
index 0000000000000000000000000000000000000000..89e18331f53c1fcf39b793a4ef8c6f8e81aa6d91
--- /dev/null
+++ b/src/sss_client/pam_message.c
@@ -0,0 +1,178 @@
+/*
+ Authors:
+ Sumit Bose <sbose at redhat.com>
+
+ PAM client - create message blob
+
+ Copyright (C) 2015 Red Hat
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <stdlib.h>
+#include <security/pam_modules.h>
+
+#include "sss_pam_compat.h"
+#include "sss_pam_macros.h"
+
+#include "pam_message.h"
+
+#include "sss_cli.h"
+
+
+static size_t add_authtok_item(enum pam_item_type type,
+ enum sss_authtok_type authtok_type,
+ const char *tok, const size_t size,
+ uint8_t *buf) {
+ size_t rp=0;
+ uint32_t c;
+
+ if (tok == NULL) return 0;
+
+ c = type;
+ memcpy(&buf[rp], &c, sizeof(uint32_t));
+ rp += sizeof(uint32_t);
+
+ c = size + sizeof(uint32_t);
+ memcpy(&buf[rp], &c, sizeof(uint32_t));
+ rp += sizeof(uint32_t);
+
+ c = authtok_type;
+ memcpy(&buf[rp], &c, sizeof(uint32_t));
+ rp += sizeof(uint32_t);
+
+ memcpy(&buf[rp], tok, size);
+ rp += size;
+
+ return rp;
+}
+
+
+static size_t add_uint32_t_item(enum pam_item_type type, const uint32_t val,
+ uint8_t *buf) {
+ size_t rp=0;
+ uint32_t c;
+
+ c = type;
+ memcpy(&buf[rp], &c, sizeof(uint32_t));
+ rp += sizeof(uint32_t);
+
+ c = sizeof(uint32_t);
+ memcpy(&buf[rp], &c, sizeof(uint32_t));
+ rp += sizeof(uint32_t);
+
+ c = val;
+ memcpy(&buf[rp], &c, sizeof(uint32_t));
+ rp += sizeof(uint32_t);
+
+ return rp;
+}
+
+static size_t add_string_item(enum pam_item_type type, const char *str,
+ const size_t size, uint8_t *buf) {
+ size_t rp=0;
+ uint32_t c;
+
+ if (str == NULL || *str == '\0') return 0;
+
+ c = type;
+ memcpy(&buf[rp], &c, sizeof(uint32_t));
+ rp += sizeof(uint32_t);
+
+ c = size;
+ memcpy(&buf[rp], &c, sizeof(uint32_t));
+ rp += sizeof(uint32_t);
+
+ memcpy(&buf[rp], str, size);
+ rp += size;
+
+ return rp;
+}
+
+int pack_message_v3(struct pam_items *pi, size_t *size, uint8_t **buffer)
+{
+ int len;
+ uint8_t *buf;
+ size_t rp;
+
+ len = sizeof(uint32_t) +
+ 2*sizeof(uint32_t) + pi->pam_user_size +
+ sizeof(uint32_t);
+ len += *pi->pam_service != '\0' ?
+ 2*sizeof(uint32_t) + pi->pam_service_size : 0;
+ len += *pi->pam_tty != '\0' ?
+ 2*sizeof(uint32_t) + pi->pam_tty_size : 0;
+ len += *pi->pam_ruser != '\0' ?
+ 2*sizeof(uint32_t) + pi->pam_ruser_size : 0;
+ len += *pi->pam_rhost != '\0' ?
+ 2*sizeof(uint32_t) + pi->pam_rhost_size : 0;
+ len += pi->pam_authtok != NULL ?
+ 3*sizeof(uint32_t) + pi->pam_authtok_size : 0;
+ len += pi->pam_newauthtok != NULL ?
+ 3*sizeof(uint32_t) + pi->pam_newauthtok_size : 0;
+ len += 3*sizeof(uint32_t); /* cli_pid */
+ len += *pi->requested_domains != '\0' ?
+ 2*sizeof(uint32_t) + pi->requested_domains_size : 0;
+
+
+ buf = malloc(len);
+ if (buf == NULL) {
+ D(("malloc failed."));
+ return PAM_BUF_ERR;
+ }
+
+ rp = 0;
+ SAFEALIGN_SETMEM_UINT32(buf, SSS_START_OF_PAM_REQUEST, &rp);
+
+ rp += add_string_item(SSS_PAM_ITEM_USER, pi->pam_user, pi->pam_user_size,
+ &buf[rp]);
+
+ rp += add_string_item(SSS_PAM_ITEM_SERVICE, pi->pam_service,
+ pi->pam_service_size, &buf[rp]);
+
+ rp += add_string_item(SSS_PAM_ITEM_TTY, pi->pam_tty, pi->pam_tty_size,
+ &buf[rp]);
+
+ rp += add_string_item(SSS_PAM_ITEM_RUSER, pi->pam_ruser, pi->pam_ruser_size,
+ &buf[rp]);
+
+ rp += add_string_item(SSS_PAM_ITEM_RHOST, pi->pam_rhost, pi->pam_rhost_size,
+ &buf[rp]);
+
+ rp += add_string_item(SSS_PAM_ITEM_REQUESTED_DOMAINS, pi->requested_domains, pi->requested_domains_size,
+ &buf[rp]);
+
+ rp += add_uint32_t_item(SSS_PAM_ITEM_CLI_PID, (uint32_t) pi->cli_pid,
+ &buf[rp]);
+
+ rp += add_authtok_item(SSS_PAM_ITEM_AUTHTOK, pi->pam_authtok_type,
+ pi->pam_authtok, pi->pam_authtok_size, &buf[rp]);
+
+ rp += add_authtok_item(SSS_PAM_ITEM_NEWAUTHTOK, pi->pam_newauthtok_type,
+ pi->pam_newauthtok, pi->pam_newauthtok_size,
+ &buf[rp]);
+
+ SAFEALIGN_SETMEM_UINT32(buf + rp, SSS_END_OF_PAM_REQUEST, &rp);
+
+ if (rp != len) {
+ D(("error during packet creation."));
+ free(buf);
+ return PAM_BUF_ERR;
+ }
+
+ *size = len;
+ *buffer = buf;
+
+ return 0;
+}
diff --git a/src/sss_client/pam_message.h b/src/sss_client/pam_message.h
new file mode 100644
index 0000000000000000000000000000000000000000..52f96bc1bd4871ccb09846f37c7d566692aa7112
--- /dev/null
+++ b/src/sss_client/pam_message.h
@@ -0,0 +1,61 @@
+/*
+ Authors:
+ Sumit Bose <sbose at redhat.com>
+
+ Copyright (C) 2015 Red Hat
+
+ PAM client - create message blob
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _PAM_MESSAGE_H_
+#define _PAM_MESSAGE_H_
+
+#include <unistd.h>
+#include <stdint.h>
+
+struct pam_items {
+ const char* pam_service;
+ const char* pam_user;
+ const char* pam_tty;
+ const char* pam_ruser;
+ const char* pam_rhost;
+ char* pam_authtok;
+ char* pam_newauthtok;
+ const char* pamstack_authtok;
+ const char* pamstack_oldauthtok;
+ size_t pam_service_size;
+ size_t pam_user_size;
+ size_t pam_tty_size;
+ size_t pam_ruser_size;
+ size_t pam_rhost_size;
+ int pam_authtok_type;
+ size_t pam_authtok_size;
+ int pam_newauthtok_type;
+ size_t pam_newauthtok_size;
+ pid_t cli_pid;
+ const char *login_name;
+ char *domain_name;
+ const char *requested_domains;
+ size_t requested_domains_size;
+ char *otp_vendor;
+ char *otp_token_id;
+ char *otp_challenge;
+ char *first_factor;
+};
+
+int pack_message_v3(struct pam_items *pi, size_t *size, uint8_t **buffer);
+
+#endif /* _PAM_MESSAGE_H_ */
diff --git a/src/sss_client/pam_sss.c b/src/sss_client/pam_sss.c
index e6cf35a90f958e2eceeb737a35e4ebfae99f73fc..fa3d198369e06d85a5528a3c4670c3fad479870d 100644
--- a/src/sss_client/pam_sss.c
+++ b/src/sss_client/pam_sss.c
@@ -40,6 +40,7 @@
#include "sss_pam_macros.h"
#include "sss_cli.h"
+#include "pam_message.h"
#include "util/atomic_io.h"
#include "util/authtok-utils.h"
@@ -65,36 +66,6 @@
#define EXP_ACC_MSG _("Permission denied. ")
#define SRV_MSG _("Server message: ")
-struct pam_items {
- const char* pam_service;
- const char* pam_user;
- const char* pam_tty;
- const char* pam_ruser;
- const char* pam_rhost;
- char* pam_authtok;
- char* pam_newauthtok;
- const char* pamstack_authtok;
- const char* pamstack_oldauthtok;
- size_t pam_service_size;
- size_t pam_user_size;
- size_t pam_tty_size;
- size_t pam_ruser_size;
- size_t pam_rhost_size;
- int pam_authtok_type;
- size_t pam_authtok_size;
- int pam_newauthtok_type;
- size_t pam_newauthtok_size;
- pid_t cli_pid;
- const char *login_name;
- char *domain_name;
- const char *requested_domains;
- size_t requested_domains_size;
- char *otp_vendor;
- char *otp_token_id;
- char *otp_challenge;
- char *first_factor;
-};
-
#define DEBUG_MGS_LEN 1024
#define MAX_AUTHTOK_SIZE (1024*1024)
#define CHECK_AND_RETURN_PI_STRING(s) ((s != NULL && *s != '\0')? s : "(not available)")
@@ -145,75 +116,6 @@ static void close_fd(pam_handle_t *pamh, void *ptr, int err)
sss_pam_close_fd();
}
-static size_t add_authtok_item(enum pam_item_type type,
- enum sss_authtok_type authtok_type,
- const char *tok, const size_t size,
- uint8_t *buf) {
- size_t rp=0;
- uint32_t c;
-
- if (tok == NULL) return 0;
-
- c = type;
- memcpy(&buf[rp], &c, sizeof(uint32_t));
- rp += sizeof(uint32_t);
-
- c = size + sizeof(uint32_t);
- memcpy(&buf[rp], &c, sizeof(uint32_t));
- rp += sizeof(uint32_t);
-
- c = authtok_type;
- memcpy(&buf[rp], &c, sizeof(uint32_t));
- rp += sizeof(uint32_t);
-
- memcpy(&buf[rp], tok, size);
- rp += size;
-
- return rp;
-}
-
-
-static size_t add_uint32_t_item(enum pam_item_type type, const uint32_t val,
- uint8_t *buf) {
- size_t rp=0;
- uint32_t c;
-
- c = type;
- memcpy(&buf[rp], &c, sizeof(uint32_t));
- rp += sizeof(uint32_t);
-
- c = sizeof(uint32_t);
- memcpy(&buf[rp], &c, sizeof(uint32_t));
- rp += sizeof(uint32_t);
-
- c = val;
- memcpy(&buf[rp], &c, sizeof(uint32_t));
- rp += sizeof(uint32_t);
-
- return rp;
-}
-
-static size_t add_string_item(enum pam_item_type type, const char *str,
- const size_t size, uint8_t *buf) {
- size_t rp=0;
- uint32_t c;
-
- if (str == NULL || *str == '\0') return 0;
-
- c = type;
- memcpy(&buf[rp], &c, sizeof(uint32_t));
- rp += sizeof(uint32_t);
-
- c = size;
- memcpy(&buf[rp], &c, sizeof(uint32_t));
- rp += sizeof(uint32_t);
-
- memcpy(&buf[rp], str, size);
- rp += size;
-
- return rp;
-}
-
static void overwrite_and_free_authtoks(struct pam_items *pi)
{
if (pi->pam_authtok != NULL) {
@@ -255,83 +157,6 @@ static void overwrite_and_free_pam_items(struct pam_items *pi)
pi->otp_challenge = NULL;
}
-static int pack_message_v3(struct pam_items *pi, size_t *size,
- uint8_t **buffer) {
- int len;
- uint8_t *buf;
- size_t rp;
-
- len = sizeof(uint32_t) +
- 2*sizeof(uint32_t) + pi->pam_user_size +
- sizeof(uint32_t);
- len += *pi->pam_service != '\0' ?
- 2*sizeof(uint32_t) + pi->pam_service_size : 0;
- len += *pi->pam_tty != '\0' ?
- 2*sizeof(uint32_t) + pi->pam_tty_size : 0;
- len += *pi->pam_ruser != '\0' ?
- 2*sizeof(uint32_t) + pi->pam_ruser_size : 0;
- len += *pi->pam_rhost != '\0' ?
- 2*sizeof(uint32_t) + pi->pam_rhost_size : 0;
- len += pi->pam_authtok != NULL ?
- 3*sizeof(uint32_t) + pi->pam_authtok_size : 0;
- len += pi->pam_newauthtok != NULL ?
- 3*sizeof(uint32_t) + pi->pam_newauthtok_size : 0;
- len += 3*sizeof(uint32_t); /* cli_pid */
- len += *pi->requested_domains != '\0' ?
- 2*sizeof(uint32_t) + pi->requested_domains_size : 0;
-
-
- buf = malloc(len);
- if (buf == NULL) {
- D(("malloc failed."));
- return PAM_BUF_ERR;
- }
-
- rp = 0;
- SAFEALIGN_SETMEM_UINT32(buf, SSS_START_OF_PAM_REQUEST, &rp);
-
- rp += add_string_item(SSS_PAM_ITEM_USER, pi->pam_user, pi->pam_user_size,
- &buf[rp]);
-
- rp += add_string_item(SSS_PAM_ITEM_SERVICE, pi->pam_service,
- pi->pam_service_size, &buf[rp]);
-
- rp += add_string_item(SSS_PAM_ITEM_TTY, pi->pam_tty, pi->pam_tty_size,
- &buf[rp]);
-
- rp += add_string_item(SSS_PAM_ITEM_RUSER, pi->pam_ruser, pi->pam_ruser_size,
- &buf[rp]);
-
- rp += add_string_item(SSS_PAM_ITEM_RHOST, pi->pam_rhost, pi->pam_rhost_size,
- &buf[rp]);
-
- rp += add_string_item(SSS_PAM_ITEM_REQUESTED_DOMAINS, pi->requested_domains, pi->requested_domains_size,
- &buf[rp]);
-
- rp += add_uint32_t_item(SSS_PAM_ITEM_CLI_PID, (uint32_t) pi->cli_pid,
- &buf[rp]);
-
- rp += add_authtok_item(SSS_PAM_ITEM_AUTHTOK, pi->pam_authtok_type,
- pi->pam_authtok, pi->pam_authtok_size, &buf[rp]);
-
- rp += add_authtok_item(SSS_PAM_ITEM_NEWAUTHTOK, pi->pam_newauthtok_type,
- pi->pam_newauthtok, pi->pam_newauthtok_size,
- &buf[rp]);
-
- SAFEALIGN_SETMEM_UINT32(buf + rp, SSS_END_OF_PAM_REQUEST, &rp);
-
- if (rp != len) {
- D(("error during packet creation."));
- free(buf);
- return PAM_BUF_ERR;
- }
-
- *size = len;
- *buffer = buf;
-
- return 0;
-}
-
static int null_strcmp(const char *s1, const char *s2) {
if (s1 == NULL && s2 == NULL) return 0;
if (s1 == NULL && s2 != NULL) return -1;
--
2.1.0
-------------- next part --------------
From dc213d592ccb2e622127cafd9fd32eff1201de83 Mon Sep 17 00:00:00 2001
From: Sumit Bose <sbose at redhat.com>
Date: Thu, 19 Mar 2015 13:12:11 +0100
Subject: [PATCH 14/14] PAM: add PAM responder unit test
---
Makefile.am | 32 ++
src/tests/cmocka/test_pam_srv.c | 907 ++++++++++++++++++++++++++++++++++++++++
2 files changed, 939 insertions(+)
create mode 100644 src/tests/cmocka/test_pam_srv.c
diff --git a/Makefile.am b/Makefile.am
index 412b4426af8d07f7f4eb4d6dcaa71ad99e9cee5f..b970bbc30e4e7414deb5a0be86ef5867e20192e4 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -224,6 +224,7 @@ if HAVE_CMOCKA
responder_cache_req-tests \
test_sbus_opath \
test_fo_srv \
+ pam-srv-tests \
$(NULL)
if HAVE_LIBRESOLV
@@ -1775,6 +1776,37 @@ nss_srv_tests_LDADD = \
libsss_test_common.la \
libsss_idmap.la
+EXTRA_pam_srv_tests_DEPENDENCIES = \
+ $(ldblib_LTLIBRARIES) \
+ $(NULL)
+pam_srv_tests_SOURCES = \
+ $(TEST_MOCK_RESP_OBJ) \
+ src/tests/cmocka/test_pam_srv.c \
+ src/sss_client/pam_message.c \
+ src/responder/pam/pamsrv_cmd.c \
+ src/responder/pam/pam_helpers.c \
+ src/responder/pam/pamsrv_dp.c \
+ src/responder/pam/pam_LOCAL_domain.c \
+ $(NULL)
+pam_srv_tests_CFLAGS = \
+ $(AM_CFLAGS) \
+ $(NULL)
+pam_srv_tests_LDFLAGS = \
+ -Wl,-wrap,sss_packet_get_body \
+ -Wl,-wrap,sss_packet_get_cmd \
+ -Wl,-wrap,sss_cmd_send_empty \
+ -Wl,-wrap,sss_cmd_done \
+ -Wl,-wrap,pam_dp_send_req \
+ $(NULL)
+pam_srv_tests_LDADD = \
+ $(CMOCKA_LIBS) \
+ $(PAM_LIBS) \
+ $(SSSD_LIBS) \
+ $(SSSD_INTERNAL_LTLIBS) \
+ libsss_test_common.la \
+ libsss_idmap.la \
+ $(NULL)
+
EXTRA_responder_get_domains_tests_DEPENDENCIES = \
$(ldblib_LTLIBRARIES)
responder_get_domains_tests_SOURCES = \
diff --git a/src/tests/cmocka/test_pam_srv.c b/src/tests/cmocka/test_pam_srv.c
new file mode 100644
index 0000000000000000000000000000000000000000..13e808f3eb5bd0b5410c7d3e5659bc9ac31298bc
--- /dev/null
+++ b/src/tests/cmocka/test_pam_srv.c
@@ -0,0 +1,907 @@
+/*
+ Authors:
+ Sumit Bose <sbose at redhat.com>
+
+ Copyright (C) 2015 Red Hat
+
+ SSSD tests: PAM responder tests
+
+ 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 <http://www.gnu.org/licenses/>.
+*/
+
+#include <security/pam_modules.h>
+#include <popt.h>
+
+#include "tests/cmocka/common_mock.h"
+#include "tests/cmocka/common_mock_resp.h"
+#include "responder/common/responder_packet.h"
+#include "responder/common/negcache.h"
+#include "responder/pam/pamsrv.h"
+#include "responder/pam/pam_helpers.h"
+#include "sss_client/pam_message.h"
+#include "sss_client/sss_cli.h"
+
+#include "util/crypto/nss/nss_util.h"
+
+#define TESTS_PATH "tests_pam"
+#define TEST_CONF_DB "test_pam_conf.ldb"
+#define TEST_DOM_NAME "pam_test"
+#define TEST_SUBDOM_NAME "test.subdomain"
+#define TEST_ID_PROVIDER "ldap"
+
+struct pam_test_ctx {
+ struct sss_test_ctx *tctx;
+ struct sss_domain_info *subdom;
+
+ struct resp_ctx *rctx;
+ struct cli_ctx *cctx;
+ struct sss_cmd_table *pam_cmds;
+ struct pam_ctx *pctx;
+
+ int ncache_hits;
+ int exp_pam_status;
+};
+
+/* Must be global because it is needed in some wrappers */
+struct pam_test_ctx *pam_test_ctx;
+
+struct pam_ctx *mock_pctx(TALLOC_CTX *mem_ctx)
+{
+ struct pam_ctx *pctx;
+ errno_t ret;
+
+ pctx = talloc_zero(mem_ctx, struct pam_ctx);
+ if (!pctx) {
+ return NULL;
+ }
+
+ ret = sss_ncache_init(pctx, &pctx->ncache);
+ if (ret != EOK) {
+ talloc_free(pctx);
+ return NULL;
+ }
+ pctx->neg_timeout = 10;
+
+ ret = sss_hash_create(pctx, 10, &pctx->id_table);
+ if (ret != EOK) {
+ talloc_free(pctx);
+ return NULL;
+ }
+
+ return pctx;
+}
+
+void test_pam_setup(struct sss_test_conf_param params[],
+ void **state)
+{
+ errno_t ret;
+
+ pam_test_ctx = talloc_zero(NULL, struct pam_test_ctx);
+ assert_non_null(pam_test_ctx);
+
+ pam_test_ctx->tctx = create_dom_test_ctx(pam_test_ctx, TESTS_PATH,
+ TEST_CONF_DB, TEST_DOM_NAME,
+ TEST_ID_PROVIDER, params);
+ assert_non_null(pam_test_ctx->tctx);
+
+ pam_test_ctx->pam_cmds = get_pam_cmds();
+ assert_non_null(pam_test_ctx->pam_cmds);
+
+ /* FIXME - perhaps this should be folded into sssd_domain_init or stricty
+ * used together
+ */
+ ret = sss_names_init(pam_test_ctx, pam_test_ctx->tctx->confdb,
+ TEST_DOM_NAME, &pam_test_ctx->tctx->dom->names);
+ assert_int_equal(ret, EOK);
+
+ /* Initialize the PAM responder */
+ pam_test_ctx->pctx = mock_pctx(pam_test_ctx);
+ assert_non_null(pam_test_ctx->pctx);
+
+ pam_test_ctx->rctx = mock_rctx(pam_test_ctx, pam_test_ctx->tctx->ev,
+ pam_test_ctx->tctx->dom, pam_test_ctx->pctx);
+ assert_non_null(pam_test_ctx->rctx);
+ pam_test_ctx->rctx->cdb = pam_test_ctx->tctx->confdb;
+ pam_test_ctx->pctx->rctx = pam_test_ctx->rctx;
+
+ /* Create client context */
+ pam_test_ctx->cctx = mock_cctx(pam_test_ctx, pam_test_ctx->rctx);
+ assert_non_null(pam_test_ctx->cctx);
+
+ pam_test_ctx->cctx->cli_protocol_version = register_cli_protocol_version();
+}
+
+static int pam_test_setup(void **state)
+{
+ int ret;
+
+ struct sss_test_conf_param params[] = {
+ { "enumerate", "false" },
+ { "cache_credentials", "true" },
+ { NULL, NULL }, /* Sentinel */
+ };
+
+ test_pam_setup(params, state);
+
+ /* Prime the cache with a valid user */
+ ret = sysdb_add_user(pam_test_ctx->tctx->dom,
+ "pamuser", 123, 456, "pam user",
+ "/home/pamuser", "/bin/sh", NULL,
+ NULL, 300, 0);
+ assert_int_equal(ret, EOK);
+
+ /* Add entry to the initgr cache to make sure no initgr request is sent to
+ * the backend */
+ ret = pam_initgr_cache_set(pam_test_ctx->pctx->rctx->ev,
+ pam_test_ctx->pctx->id_table,
+ discard_const("pamuser"),
+ pam_test_ctx->pctx->id_timeout);
+ assert_int_equal(ret, EOK);
+ return 0;
+}
+
+static int pam_test_teardown(void **state)
+{
+ int ret;
+
+ ret = sysdb_delete_user(pam_test_ctx->tctx->dom, "pamuser", 0);
+ assert_int_equal(ret, EOK);
+
+ talloc_free(pam_test_ctx);
+ return 0;
+}
+
+typedef int (*cmd_cb_fn_t)(uint32_t, uint8_t *, size_t );
+
+void __real_sss_packet_get_body(struct sss_packet *packet,
+ uint8_t **body, size_t *blen);
+
+void __wrap_sss_packet_get_body(struct sss_packet *packet,
+ uint8_t **body, size_t *blen)
+{
+ enum sss_test_wrapper_call wtype = sss_mock_type(enum sss_test_wrapper_call);
+ size_t len;
+
+ if (wtype == WRAP_CALL_REAL) {
+ return __real_sss_packet_get_body(packet, body, blen);
+ }
+
+ *body = sss_mock_ptr_type(uint8_t *);
+ len = sss_mock_type(size_t);
+ if (len == 0) {
+ len = strlen((const char *) *body)+1;
+ }
+ *blen = len;
+ return;
+}
+
+void __real_sss_packet_get_body(struct sss_packet *packet,
+ uint8_t **body, size_t *blen);
+
+void __wrap_sss_cmd_done(struct cli_ctx *cctx, void *freectx)
+{
+ struct sss_packet *packet = cctx->creq->out;
+ uint8_t *body;
+ size_t blen;
+ cmd_cb_fn_t check_cb;
+
+ assert_non_null(packet);
+
+ check_cb = sss_mock_ptr_type(cmd_cb_fn_t);
+
+ __real_sss_packet_get_body(packet, &body, &blen);
+
+ pam_test_ctx->tctx->error = check_cb(sss_packet_get_status(packet),
+ body, blen);
+ pam_test_ctx->tctx->done = true;
+}
+
+enum sss_cli_command __wrap_sss_packet_get_cmd(struct sss_packet *packet)
+{
+ return sss_mock_type(enum sss_cli_command);
+}
+
+int __wrap_sss_cmd_send_empty(struct cli_ctx *cctx, TALLOC_CTX *freectx)
+{
+ pam_test_ctx->tctx->done = true;
+ pam_test_ctx->tctx->error = ENOENT;
+ return EOK;
+}
+
+static void set_cmd_cb(cmd_cb_fn_t fn)
+{
+ will_return(__wrap_sss_cmd_done, fn);
+}
+
+int __wrap_pam_dp_send_req(struct pam_auth_req *preq, int timeout)
+{
+
+ /* Set expected status */
+ preq->pd->pam_status = pam_test_ctx->exp_pam_status;
+
+ preq->callback(preq);
+
+ return EOK;
+}
+
+static void mock_input_pam(TALLOC_CTX *mem_ctx, const char *name,
+ const char *pwd, const char *fa2)
+{
+ size_t buf_size;
+ uint8_t *m_buf;
+ uint8_t *buf;
+ struct pam_items pi = { 0 };
+ int ret;
+ size_t needed_size;
+ uint8_t *authtok;
+
+ pi.pam_user = name;
+ pi.pam_user_size = strlen(pi.pam_user) + 1;
+
+ if (pwd != NULL) {
+ if (fa2 != NULL) {
+ ret = sss_auth_pack_2fa_blob(pwd, 0, fa2, 0, NULL, 0, &needed_size);
+ assert_int_equal(ret, EAGAIN);
+
+ authtok = talloc_size(mem_ctx, needed_size);
+ assert_non_null(authtok);
+
+ ret = sss_auth_pack_2fa_blob(pwd, 0, fa2, 0, authtok,
+ needed_size, &needed_size);
+ assert_int_equal(ret, EOK);
+
+ pi.pam_authtok = (char *) authtok;
+ pi.pam_authtok_size = needed_size;
+ pi.pam_authtok_type = SSS_AUTHTOK_TYPE_2FA;
+ } else {
+ pi.pam_authtok = discard_const(pwd);
+ pi.pam_authtok_size = strlen(pi.pam_authtok) + 1;
+ pi.pam_authtok_type = SSS_AUTHTOK_TYPE_PASSWORD;
+ }
+ }
+
+ pi.pam_service = "ssh";
+ pi.pam_service_size=strlen(pi.pam_service)+1;
+ pi.pam_tty = "/dev/tty";
+ pi.pam_tty_size=strlen(pi.pam_tty)+1;
+ pi.pam_ruser = "remuser";
+ pi.pam_ruser_size=strlen(pi.pam_ruser)+1;
+ pi.pam_rhost = "remhost";
+ pi.pam_rhost_size=strlen(pi.pam_rhost)+1;
+ pi.requested_domains = "";
+ pi.cli_pid = 12345;
+
+ ret = pack_message_v3(&pi, &buf_size, &m_buf);
+ assert_int_equal(ret, 0);
+
+ buf = talloc_memdup(mem_ctx, m_buf, buf_size);
+ free(m_buf);
+ assert_non_null(buf);
+
+ will_return(__wrap_sss_packet_get_body, WRAP_CALL_WRAPPER);
+ will_return(__wrap_sss_packet_get_body, buf);
+ will_return(__wrap_sss_packet_get_body, buf_size);
+}
+
+static int test_pam_simple_check(uint32_t status, uint8_t *body, size_t blen)
+{
+ size_t rp = 0;
+ uint32_t val;
+
+ assert_int_equal(status, 0);
+
+ SAFEALIGN_COPY_UINT32(&val, body+rp, &rp);
+ assert_int_equal(val, pam_test_ctx->exp_pam_status);
+
+ SAFEALIGN_COPY_UINT32(&val, body+rp, &rp);
+ assert_int_equal(val, 1);
+
+ SAFEALIGN_COPY_UINT32(&val, body+rp, &rp);
+ assert_int_equal(val, SSS_PAM_DOMAIN_NAME);
+
+ SAFEALIGN_COPY_UINT32(&val, body+rp, &rp);
+ assert_int_equal(val, 9);
+
+ assert_int_equal(*(body + rp + val - 1), 0);
+ assert_string_equal(body+rp, TEST_DOM_NAME);
+
+ return EOK;
+}
+
+static int test_pam_failed_offline_auth_check(uint32_t status, uint8_t *body,
+ size_t blen)
+{
+ pam_test_ctx->exp_pam_status = PAM_PERM_DENIED;
+ return test_pam_simple_check(status, body, blen);
+}
+
+static int test_pam_successful_offline_auth_check(uint32_t status,
+ uint8_t *body, size_t blen)
+{
+ pam_test_ctx->exp_pam_status = PAM_SUCCESS;
+ return test_pam_simple_check(status, body, blen);
+}
+
+static int test_pam_wrong_pw_offline_auth_check(uint32_t status,
+ uint8_t *body, size_t blen)
+{
+ pam_test_ctx->exp_pam_status = PAM_AUTH_ERR;
+ return test_pam_simple_check(status, body, blen);
+}
+
+void test_pam_authenticate(void **state)
+{
+ int ret;
+
+ mock_input_pam(pam_test_ctx, "pamuser", NULL, NULL);
+
+ will_return(__wrap_sss_packet_get_cmd, SSS_PAM_AUTHENTICATE);
+ will_return(__wrap_sss_packet_get_body, WRAP_CALL_REAL);
+
+ set_cmd_cb(test_pam_simple_check);
+ ret = sss_cmd_execute(pam_test_ctx->cctx, SSS_PAM_AUTHENTICATE,
+ pam_test_ctx->pam_cmds);
+ assert_int_equal(ret, EOK);
+
+ /* Wait until the test finishes with EOK */
+ ret = test_ev_loop(pam_test_ctx->tctx);
+ assert_int_equal(ret, EOK);
+
+}
+
+void test_pam_setcreds(void **state)
+{
+ int ret;
+
+ mock_input_pam(pam_test_ctx, "pamuser", NULL, NULL);
+
+ will_return(__wrap_sss_packet_get_cmd, SSS_PAM_SETCRED);
+ will_return(__wrap_sss_packet_get_body, WRAP_CALL_REAL);
+
+ set_cmd_cb(test_pam_simple_check);
+ ret = sss_cmd_execute(pam_test_ctx->cctx, SSS_PAM_SETCRED,
+ pam_test_ctx->pam_cmds);
+ assert_int_equal(ret, EOK);
+
+ /* Wait until the test finishes with EOK */
+ ret = test_ev_loop(pam_test_ctx->tctx);
+ assert_int_equal(ret, EOK);
+
+}
+
+void test_pam_acct_mgmt(void **state)
+{
+ int ret;
+
+ mock_input_pam(pam_test_ctx, "pamuser", NULL, NULL);
+
+ will_return(__wrap_sss_packet_get_cmd, SSS_PAM_ACCT_MGMT);
+ will_return(__wrap_sss_packet_get_body, WRAP_CALL_REAL);
+
+ set_cmd_cb(test_pam_simple_check);
+ ret = sss_cmd_execute(pam_test_ctx->cctx, SSS_PAM_ACCT_MGMT,
+ pam_test_ctx->pam_cmds);
+ assert_int_equal(ret, EOK);
+
+ /* Wait until the test finishes with EOK */
+ ret = test_ev_loop(pam_test_ctx->tctx);
+ assert_int_equal(ret, EOK);
+
+}
+
+
+void test_pam_open_session(void **state)
+{
+ int ret;
+
+ mock_input_pam(pam_test_ctx, "pamuser", NULL, NULL);
+
+ will_return(__wrap_sss_packet_get_cmd, SSS_PAM_OPEN_SESSION);
+ will_return(__wrap_sss_packet_get_body, WRAP_CALL_REAL);
+
+ pam_test_ctx->exp_pam_status = PAM_NO_MODULE_DATA;
+ set_cmd_cb(test_pam_simple_check);
+ ret = sss_cmd_execute(pam_test_ctx->cctx, SSS_PAM_OPEN_SESSION,
+ pam_test_ctx->pam_cmds);
+ assert_int_equal(ret, EOK);
+
+ /* Wait until the test finishes with EOK */
+ ret = test_ev_loop(pam_test_ctx->tctx);
+ assert_int_equal(ret, EOK);
+
+}
+
+void test_pam_close_session(void **state)
+{
+ int ret;
+
+ mock_input_pam(pam_test_ctx, "pamuser", NULL, NULL);
+
+ will_return(__wrap_sss_packet_get_cmd, SSS_PAM_CLOSE_SESSION);
+ will_return(__wrap_sss_packet_get_body, WRAP_CALL_REAL);
+
+ set_cmd_cb(test_pam_simple_check);
+ ret = sss_cmd_execute(pam_test_ctx->cctx, SSS_PAM_CLOSE_SESSION,
+ pam_test_ctx->pam_cmds);
+ assert_int_equal(ret, EOK);
+
+ /* Wait until the test finishes with EOK */
+ ret = test_ev_loop(pam_test_ctx->tctx);
+ assert_int_equal(ret, EOK);
+
+}
+
+void test_pam_chauthtok(void **state)
+{
+ int ret;
+
+ mock_input_pam(pam_test_ctx, "pamuser", NULL, NULL);
+
+ will_return(__wrap_sss_packet_get_cmd, SSS_PAM_CHAUTHTOK);
+ will_return(__wrap_sss_packet_get_body, WRAP_CALL_REAL);
+
+ set_cmd_cb(test_pam_simple_check);
+ ret = sss_cmd_execute(pam_test_ctx->cctx, SSS_PAM_CHAUTHTOK,
+ pam_test_ctx->pam_cmds);
+ assert_int_equal(ret, EOK);
+
+ /* Wait until the test finishes with EOK */
+ ret = test_ev_loop(pam_test_ctx->tctx);
+ assert_int_equal(ret, EOK);
+
+}
+
+void test_pam_chauthtok_prelim(void **state)
+{
+ int ret;
+
+ mock_input_pam(pam_test_ctx, "pamuser", NULL, NULL);
+
+ will_return(__wrap_sss_packet_get_cmd, SSS_PAM_CHAUTHTOK_PRELIM);
+ will_return(__wrap_sss_packet_get_body, WRAP_CALL_REAL);
+
+ set_cmd_cb(test_pam_simple_check);
+ ret = sss_cmd_execute(pam_test_ctx->cctx, SSS_PAM_CHAUTHTOK_PRELIM,
+ pam_test_ctx->pam_cmds);
+ assert_int_equal(ret, EOK);
+
+ /* Wait until the test finishes with EOK */
+ ret = test_ev_loop(pam_test_ctx->tctx);
+ assert_int_equal(ret, EOK);
+
+}
+
+void test_pam_preauth(void **state)
+{
+ int ret;
+
+ mock_input_pam(pam_test_ctx, "pamuser", NULL, NULL);
+
+ will_return(__wrap_sss_packet_get_cmd, SSS_PAM_PREAUTH);
+ will_return(__wrap_sss_packet_get_body, WRAP_CALL_REAL);
+
+ set_cmd_cb(test_pam_simple_check);
+ ret = sss_cmd_execute(pam_test_ctx->cctx, SSS_PAM_PREAUTH,
+ pam_test_ctx->pam_cmds);
+ assert_int_equal(ret, EOK);
+
+ /* Wait until the test finishes with EOK */
+ ret = test_ev_loop(pam_test_ctx->tctx);
+ assert_int_equal(ret, EOK);
+
+}
+
+void test_pam_offline_auth_no_hash(void **state)
+{
+ int ret;
+
+ mock_input_pam(pam_test_ctx, "pamuser", "12345", NULL);
+
+ will_return(__wrap_sss_packet_get_cmd, SSS_PAM_AUTHENTICATE);
+ will_return(__wrap_sss_packet_get_body, WRAP_CALL_REAL);
+
+ pam_test_ctx->exp_pam_status = PAM_AUTHINFO_UNAVAIL;
+
+ set_cmd_cb(test_pam_failed_offline_auth_check);
+ ret = sss_cmd_execute(pam_test_ctx->cctx, SSS_PAM_AUTHENTICATE,
+ pam_test_ctx->pam_cmds);
+ assert_int_equal(ret, EOK);
+
+ /* Wait until the test finishes with EOK */
+ ret = test_ev_loop(pam_test_ctx->tctx);
+ assert_int_equal(ret, EOK);
+
+}
+
+void test_pam_offline_auth_success(void **state)
+{
+ int ret;
+
+ ret = sysdb_cache_password(pam_test_ctx->tctx->dom, "pamuser", "12345");
+ assert_int_equal(ret, EOK);
+
+ mock_input_pam(pam_test_ctx, "pamuser", "12345", NULL);
+
+ will_return(__wrap_sss_packet_get_cmd, SSS_PAM_AUTHENTICATE);
+ will_return(__wrap_sss_packet_get_body, WRAP_CALL_REAL);
+
+ pam_test_ctx->exp_pam_status = PAM_AUTHINFO_UNAVAIL;
+
+ set_cmd_cb(test_pam_successful_offline_auth_check);
+ ret = sss_cmd_execute(pam_test_ctx->cctx, SSS_PAM_AUTHENTICATE,
+ pam_test_ctx->pam_cmds);
+ assert_int_equal(ret, EOK);
+
+ /* Wait until the test finishes with EOK */
+ ret = test_ev_loop(pam_test_ctx->tctx);
+ assert_int_equal(ret, EOK);
+
+}
+
+void test_pam_offline_auth_wrong_pw(void **state)
+{
+ int ret;
+
+ ret = sysdb_cache_password(pam_test_ctx->tctx->dom, "pamuser", "12345");
+ assert_int_equal(ret, EOK);
+
+ mock_input_pam(pam_test_ctx, "pamuser", "11111", NULL);
+
+ will_return(__wrap_sss_packet_get_cmd, SSS_PAM_AUTHENTICATE);
+ will_return(__wrap_sss_packet_get_body, WRAP_CALL_REAL);
+
+ pam_test_ctx->exp_pam_status = PAM_AUTHINFO_UNAVAIL;
+
+ set_cmd_cb(test_pam_wrong_pw_offline_auth_check);
+ ret = sss_cmd_execute(pam_test_ctx->cctx, SSS_PAM_AUTHENTICATE,
+ pam_test_ctx->pam_cmds);
+ assert_int_equal(ret, EOK);
+
+ /* Wait until the test finishes with EOK */
+ ret = test_ev_loop(pam_test_ctx->tctx);
+ assert_int_equal(ret, EOK);
+
+}
+
+void test_pam_offline_auth_success_2fa(void **state)
+{
+ int ret;
+
+ ret = sysdb_cache_password(pam_test_ctx->tctx->dom, "pamuser", "12345");
+ assert_int_equal(ret, EOK);
+
+ mock_input_pam(pam_test_ctx, "pamuser", "12345", "abcde");
+
+ will_return(__wrap_sss_packet_get_cmd, SSS_PAM_AUTHENTICATE);
+ will_return(__wrap_sss_packet_get_body, WRAP_CALL_REAL);
+
+ pam_test_ctx->exp_pam_status = PAM_AUTHINFO_UNAVAIL;
+
+ set_cmd_cb(test_pam_successful_offline_auth_check);
+ ret = sss_cmd_execute(pam_test_ctx->cctx, SSS_PAM_AUTHENTICATE,
+ pam_test_ctx->pam_cmds);
+ assert_int_equal(ret, EOK);
+
+ /* Wait until the test finishes with EOK */
+ ret = test_ev_loop(pam_test_ctx->tctx);
+ assert_int_equal(ret, EOK);
+
+}
+
+void test_pam_offline_auth_failed_2fa(void **state)
+{
+ int ret;
+
+ ret = sysdb_cache_password(pam_test_ctx->tctx->dom, "pamuser", "12345");
+ assert_int_equal(ret, EOK);
+
+ mock_input_pam(pam_test_ctx, "pamuser", "11111", "abcde");
+
+ will_return(__wrap_sss_packet_get_cmd, SSS_PAM_AUTHENTICATE);
+ will_return(__wrap_sss_packet_get_body, WRAP_CALL_REAL);
+
+ pam_test_ctx->exp_pam_status = PAM_AUTHINFO_UNAVAIL;
+
+ set_cmd_cb(test_pam_wrong_pw_offline_auth_check);
+ ret = sss_cmd_execute(pam_test_ctx->cctx, SSS_PAM_AUTHENTICATE,
+ pam_test_ctx->pam_cmds);
+ assert_int_equal(ret, EOK);
+
+ /* Wait until the test finishes with EOK */
+ ret = test_ev_loop(pam_test_ctx->tctx);
+ assert_int_equal(ret, EOK);
+
+}
+
+void test_pam_offline_auth_success_2fa_with_cached_2fa(void **state)
+{
+ int ret;
+
+ ret = sysdb_cache_password_ex(pam_test_ctx->tctx->dom, "pamuser", "12345",
+ SSS_AUTHTOK_TYPE_2FA, 5);
+ assert_int_equal(ret, EOK);
+
+ mock_input_pam(pam_test_ctx, "pamuser", "12345", "abcde");
+
+ will_return(__wrap_sss_packet_get_cmd, SSS_PAM_AUTHENTICATE);
+ will_return(__wrap_sss_packet_get_body, WRAP_CALL_REAL);
+
+ pam_test_ctx->exp_pam_status = PAM_AUTHINFO_UNAVAIL;
+
+ set_cmd_cb(test_pam_successful_offline_auth_check);
+ ret = sss_cmd_execute(pam_test_ctx->cctx, SSS_PAM_AUTHENTICATE,
+ pam_test_ctx->pam_cmds);
+ assert_int_equal(ret, EOK);
+
+ /* Wait until the test finishes with EOK */
+ ret = test_ev_loop(pam_test_ctx->tctx);
+ assert_int_equal(ret, EOK);
+
+}
+
+void test_pam_offline_auth_failed_2fa_with_cached_2fa(void **state)
+{
+ int ret;
+
+ ret = sysdb_cache_password_ex(pam_test_ctx->tctx->dom, "pamuser", "12345",
+ SSS_AUTHTOK_TYPE_2FA, 5);
+ assert_int_equal(ret, EOK);
+
+ mock_input_pam(pam_test_ctx, "pamuser", "11111", "abcde");
+
+ will_return(__wrap_sss_packet_get_cmd, SSS_PAM_AUTHENTICATE);
+ will_return(__wrap_sss_packet_get_body, WRAP_CALL_REAL);
+
+ pam_test_ctx->exp_pam_status = PAM_AUTHINFO_UNAVAIL;
+
+ set_cmd_cb(test_pam_wrong_pw_offline_auth_check);
+ ret = sss_cmd_execute(pam_test_ctx->cctx, SSS_PAM_AUTHENTICATE,
+ pam_test_ctx->pam_cmds);
+ assert_int_equal(ret, EOK);
+
+ /* Wait until the test finishes with EOK */
+ ret = test_ev_loop(pam_test_ctx->tctx);
+ assert_int_equal(ret, EOK);
+
+}
+
+void test_pam_offline_auth_success_pw_with_cached_2fa(void **state)
+{
+ int ret;
+
+ ret = sysdb_cache_password_ex(pam_test_ctx->tctx->dom, "pamuser", "12345",
+ SSS_AUTHTOK_TYPE_2FA, 5);
+ assert_int_equal(ret, EOK);
+
+ mock_input_pam(pam_test_ctx, "pamuser", "12345", NULL);
+
+ will_return(__wrap_sss_packet_get_cmd, SSS_PAM_AUTHENTICATE);
+ will_return(__wrap_sss_packet_get_body, WRAP_CALL_REAL);
+
+ pam_test_ctx->exp_pam_status = PAM_AUTHINFO_UNAVAIL;
+
+ set_cmd_cb(test_pam_successful_offline_auth_check);
+ ret = sss_cmd_execute(pam_test_ctx->cctx, SSS_PAM_AUTHENTICATE,
+ pam_test_ctx->pam_cmds);
+ assert_int_equal(ret, EOK);
+
+ /* Wait until the test finishes with EOK */
+ ret = test_ev_loop(pam_test_ctx->tctx);
+ assert_int_equal(ret, EOK);
+
+}
+
+void test_pam_offline_auth_failed_pw_with_cached_2fa(void **state)
+{
+ int ret;
+
+ ret = sysdb_cache_password_ex(pam_test_ctx->tctx->dom, "pamuser", "12345",
+ SSS_AUTHTOK_TYPE_2FA, 5);
+ assert_int_equal(ret, EOK);
+
+ mock_input_pam(pam_test_ctx, "pamuser", "11111", NULL);
+
+ will_return(__wrap_sss_packet_get_cmd, SSS_PAM_AUTHENTICATE);
+ will_return(__wrap_sss_packet_get_body, WRAP_CALL_REAL);
+
+ pam_test_ctx->exp_pam_status = PAM_AUTHINFO_UNAVAIL;
+
+ set_cmd_cb(test_pam_wrong_pw_offline_auth_check);
+ ret = sss_cmd_execute(pam_test_ctx->cctx, SSS_PAM_AUTHENTICATE,
+ pam_test_ctx->pam_cmds);
+ assert_int_equal(ret, EOK);
+
+ /* Wait until the test finishes with EOK */
+ ret = test_ev_loop(pam_test_ctx->tctx);
+ assert_int_equal(ret, EOK);
+
+}
+
+void test_pam_offline_auth_success_combined_pw_with_cached_2fa(void **state)
+{
+ int ret;
+
+ ret = sysdb_cache_password_ex(pam_test_ctx->tctx->dom, "pamuser",
+ "12345678", SSS_AUTHTOK_TYPE_2FA, 5);
+ assert_int_equal(ret, EOK);
+
+ mock_input_pam(pam_test_ctx, "pamuser", "12345678abcde", NULL);
+
+ will_return(__wrap_sss_packet_get_cmd, SSS_PAM_AUTHENTICATE);
+ will_return(__wrap_sss_packet_get_body, WRAP_CALL_REAL);
+
+ pam_test_ctx->exp_pam_status = PAM_AUTHINFO_UNAVAIL;
+
+ set_cmd_cb(test_pam_successful_offline_auth_check);
+ ret = sss_cmd_execute(pam_test_ctx->cctx, SSS_PAM_AUTHENTICATE,
+ pam_test_ctx->pam_cmds);
+ assert_int_equal(ret, EOK);
+
+ /* Wait until the test finishes with EOK */
+ ret = test_ev_loop(pam_test_ctx->tctx);
+ assert_int_equal(ret, EOK);
+
+}
+
+void test_pam_offline_auth_failed_combined_pw_with_cached_2fa(void **state)
+{
+ int ret;
+
+ ret = sysdb_cache_password_ex(pam_test_ctx->tctx->dom, "pamuser",
+ "12345678", SSS_AUTHTOK_TYPE_2FA, 5);
+ assert_int_equal(ret, EOK);
+
+ mock_input_pam(pam_test_ctx, "pamuser", "11111111abcde", NULL);
+
+ will_return(__wrap_sss_packet_get_cmd, SSS_PAM_AUTHENTICATE);
+ will_return(__wrap_sss_packet_get_body, WRAP_CALL_REAL);
+
+ pam_test_ctx->exp_pam_status = PAM_AUTHINFO_UNAVAIL;
+
+ set_cmd_cb(test_pam_wrong_pw_offline_auth_check);
+ ret = sss_cmd_execute(pam_test_ctx->cctx, SSS_PAM_AUTHENTICATE,
+ pam_test_ctx->pam_cmds);
+ assert_int_equal(ret, EOK);
+
+ /* Wait until the test finishes with EOK */
+ ret = test_ev_loop(pam_test_ctx->tctx);
+ assert_int_equal(ret, EOK);
+
+}
+
+void test_pam_offline_auth_failed_wrong_2fa_size_with_cached_2fa(void **state)
+{
+ int ret;
+
+ ret = sysdb_cache_password_ex(pam_test_ctx->tctx->dom, "pamuser",
+ "12345678", SSS_AUTHTOK_TYPE_2FA, 5);
+ assert_int_equal(ret, EOK);
+
+ mock_input_pam(pam_test_ctx, "pamuser", "12345678abcd", NULL);
+
+ will_return(__wrap_sss_packet_get_cmd, SSS_PAM_AUTHENTICATE);
+ will_return(__wrap_sss_packet_get_body, WRAP_CALL_REAL);
+
+ pam_test_ctx->exp_pam_status = PAM_AUTHINFO_UNAVAIL;
+
+ set_cmd_cb(test_pam_wrong_pw_offline_auth_check);
+ ret = sss_cmd_execute(pam_test_ctx->cctx, SSS_PAM_AUTHENTICATE,
+ pam_test_ctx->pam_cmds);
+ assert_int_equal(ret, EOK);
+
+ /* Wait until the test finishes with EOK */
+ ret = test_ev_loop(pam_test_ctx->tctx);
+ assert_int_equal(ret, EOK);
+
+}
+
+int main(int argc, const char *argv[])
+{
+ int rv;
+ int no_cleanup = 0;
+ poptContext pc;
+ int opt;
+ struct poptOption long_options[] = {
+ POPT_AUTOHELP
+ SSSD_DEBUG_OPTS
+ {"no-cleanup", 'n', POPT_ARG_NONE, &no_cleanup, 0,
+ _("Do not delete the test database after a test run"), NULL },
+ POPT_TABLEEND
+ };
+
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test_setup_teardown(test_pam_authenticate,
+ pam_test_setup, pam_test_teardown),
+ cmocka_unit_test_setup_teardown(test_pam_setcreds,
+ pam_test_setup, pam_test_teardown),
+ cmocka_unit_test_setup_teardown(test_pam_acct_mgmt,
+ pam_test_setup, pam_test_teardown),
+ cmocka_unit_test_setup_teardown(test_pam_open_session,
+ pam_test_setup, pam_test_teardown),
+ cmocka_unit_test_setup_teardown(test_pam_close_session,
+ pam_test_setup, pam_test_teardown),
+ cmocka_unit_test_setup_teardown(test_pam_chauthtok,
+ pam_test_setup, pam_test_teardown),
+ cmocka_unit_test_setup_teardown(test_pam_chauthtok_prelim,
+ pam_test_setup, pam_test_teardown),
+ cmocka_unit_test_setup_teardown(test_pam_preauth,
+ pam_test_setup, pam_test_teardown),
+ cmocka_unit_test_setup_teardown(test_pam_offline_auth_no_hash,
+ pam_test_setup, pam_test_teardown),
+ cmocka_unit_test_setup_teardown(test_pam_offline_auth_success,
+ pam_test_setup, pam_test_teardown),
+ cmocka_unit_test_setup_teardown(test_pam_offline_auth_wrong_pw,
+ pam_test_setup, pam_test_teardown),
+ cmocka_unit_test_setup_teardown(test_pam_offline_auth_success_2fa,
+ pam_test_setup, pam_test_teardown),
+ cmocka_unit_test_setup_teardown(test_pam_offline_auth_failed_2fa,
+ pam_test_setup, pam_test_teardown),
+ cmocka_unit_test_setup_teardown(
+ test_pam_offline_auth_success_2fa_with_cached_2fa,
+ pam_test_setup, pam_test_teardown),
+ cmocka_unit_test_setup_teardown(
+ test_pam_offline_auth_failed_2fa_with_cached_2fa,
+ pam_test_setup, pam_test_teardown),
+ cmocka_unit_test_setup_teardown(
+ test_pam_offline_auth_success_pw_with_cached_2fa,
+ pam_test_setup, pam_test_teardown),
+ cmocka_unit_test_setup_teardown(
+ test_pam_offline_auth_failed_pw_with_cached_2fa,
+ pam_test_setup, pam_test_teardown),
+ cmocka_unit_test_setup_teardown(
+ test_pam_offline_auth_success_combined_pw_with_cached_2fa,
+ pam_test_setup, pam_test_teardown),
+ cmocka_unit_test_setup_teardown(
+ test_pam_offline_auth_failed_combined_pw_with_cached_2fa,
+ pam_test_setup, pam_test_teardown),
+ cmocka_unit_test_setup_teardown(
+ test_pam_offline_auth_failed_wrong_2fa_size_with_cached_2fa,
+ pam_test_setup, pam_test_teardown),
+ };
+
+ /* Set debug level to invalid value so we can deside if -d 0 was used. */
+ debug_level = SSSDBG_INVALID;
+
+ pc = poptGetContext(argv[0], argc, argv, long_options, 0);
+ while((opt = poptGetNextOpt(pc)) != -1) {
+ switch(opt) {
+ default:
+ fprintf(stderr, "\nInvalid option %s: %s\n\n",
+ poptBadOption(pc, 0), poptStrerror(opt));
+ poptPrintUsage(pc, stderr, 0);
+ return 1;
+ }
+ }
+ poptFreeContext(pc);
+
+ DEBUG_CLI_INIT(debug_level);
+
+ /* Even though normally the tests should clean up after themselves
+ * they might not after a failed run. Remove the old db to be sure */
+ tests_set_cwd();
+ test_dom_suite_cleanup(TESTS_PATH, TEST_CONF_DB, TEST_DOM_NAME);
+ test_dom_suite_setup(TESTS_PATH);
+
+ rv = cmocka_run_group_tests(tests, NULL, NULL);
+ if (rv == 0 && !no_cleanup) {
+ test_dom_suite_cleanup(TESTS_PATH, TEST_CONF_DB, TEST_DOM_NAME);
+ }
+
+#ifdef HAVE_NSS
+ /* Cleanup NSS and NSPR to make valgrund happy. */
+ nspr_nss_cleanup();
+#endif
+
+ return rv;
+}
--
2.1.0
More information about the sssd-devel
mailing list