This series of patches implements authentication indicator insertion,
evaluation and management in FreeIPA. Besides these patches, two other
patches are needed to round out support.

First, we need a UI patch: https://fedorahosted.org/freeipa/ticket/5872

Second, we need a SSSD patch to handle the new case where multiple
responders are set (when either 1FA or 2FA can be used).

Please note that the last patch in this series (0093) is untested and
simply represents my desire to get these patches off of my hard disk
before I take a long weekend. This patch also requires mrogers' patch
0001 (already merged to master).

Also worthy of note is the need for an OID for the authentication
control. Hopefully Simo can assign this after we agree that this
control method is sufficient. One question I had was whether or not it
would be possible to send the control only on UNIX sockets (0089;
report_auth_method()).

Please review the approaches taken here. I plan to hit this hard on
Monday.

Nathaniel
From 047a8846fb5582ac1a1451c106ebf74079c3609f Mon Sep 17 00:00:00 2001
From: Nathaniel McCallum <npmccal...@redhat.com>
Date: Wed, 4 May 2016 17:08:45 -0400
Subject: [PATCH 5/5] Enable managing authentication indicators on services

TODO: actually test this patch
---
 API.txt                   | 9 ++++++---
 VERSION                   | 4 ++--
 ipalib/plugins/service.py | 8 ++++++++
 3 files changed, 16 insertions(+), 5 deletions(-)

diff --git a/API.txt b/API.txt
index 3598b08198cae536754259f7463669052efa3f86..9a563520db7b171ff655e081358876259d4a2753 100644
--- a/API.txt
+++ b/API.txt
@@ -3862,7 +3862,7 @@ output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDA
 output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
 output: PrimaryKey('value', None, None)
 command: service_add
-args: 1,11,3
+args: 1,12,3
 arg: Str('krbprincipalname', attribute=True, cli_name='principal', multivalue=False, primary_key=True, required=True)
 option: Str('addattr*', cli_name='addattr', exclude='webui')
 option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui')
@@ -3870,6 +3870,7 @@ option: Flag('force', autofill=True, default=False)
 option: StrEnum('ipakrbauthzdata', attribute=True, cli_name='pac_type', csv=True, multivalue=True, required=False, values=(u'MS-PAC', u'PAD', u'NONE'))
 option: Bool('ipakrbokasdelegate', attribute=False, cli_name='ok_as_delegate', multivalue=False, required=False)
 option: Bool('ipakrbrequirespreauth', attribute=False, cli_name='requires_pre_auth', multivalue=False, required=False)
+option: StrEnum('krbprincipalauthind', attribute=True, cli_name='auth_ind', multivalue=True, required=False, values=(u'otp', u'radius'))
 option: Flag('no_members', autofill=True, default=False, exclude='webui')
 option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui')
 option: Str('setattr*', cli_name='setattr', exclude='webui')
@@ -3972,10 +3973,11 @@ output: Output('completed', <type 'int'>, None)
 output: Output('failed', <type 'dict'>, None)
 output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None))
 command: service_find
-args: 1,11,4
+args: 1,12,4
 arg: Str('criteria?', noextrawhitespace=False)
 option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui')
 option: StrEnum('ipakrbauthzdata', attribute=True, autofill=False, cli_name='pac_type', csv=True, multivalue=True, query=True, required=False, values=(u'MS-PAC', u'PAD', u'NONE'))
+option: StrEnum('krbprincipalauthind', attribute=True, autofill=False, cli_name='auth_ind', multivalue=True, query=True, required=False, values=(u'otp', u'radius'))
 option: Str('krbprincipalname', attribute=True, autofill=False, cli_name='principal', multivalue=False, primary_key=True, query=True, required=False)
 option: Str('man_by_host*', cli_name='man_by_hosts', csv=True)
 option: Flag('no_members', autofill=True, default=False, exclude='webui')
@@ -3990,7 +3992,7 @@ output: ListOfEntries('result', (<type 'list'>, <type 'tuple'>), Gettext('A list
 output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
 output: Output('truncated', <type 'bool'>, None)
 command: service_mod
-args: 1,12,3
+args: 1,13,3
 arg: Str('krbprincipalname', attribute=True, cli_name='principal', multivalue=False, primary_key=True, query=True, required=True)
 option: Str('addattr*', cli_name='addattr', exclude='webui')
 option: Flag('all', autofill=True, cli_name='all', default=False, exclude='webui')
@@ -3998,6 +4000,7 @@ option: Str('delattr*', cli_name='delattr', exclude='webui')
 option: StrEnum('ipakrbauthzdata', attribute=True, autofill=False, cli_name='pac_type', csv=True, multivalue=True, required=False, values=(u'MS-PAC', u'PAD', u'NONE'))
 option: Bool('ipakrbokasdelegate', attribute=False, autofill=False, cli_name='ok_as_delegate', multivalue=False, required=False)
 option: Bool('ipakrbrequirespreauth', attribute=False, autofill=False, cli_name='requires_pre_auth', multivalue=False, required=False)
+option: StrEnum('krbprincipalauthind', attribute=True, autofill=False, cli_name='auth_ind', multivalue=True, required=False, values=(u'otp', u'radius'))
 option: Flag('no_members', autofill=True, default=False, exclude='webui')
 option: Flag('raw', autofill=True, cli_name='raw', default=False, exclude='webui')
 option: Flag('rights', autofill=True, default=False)
diff --git a/VERSION b/VERSION
index aedebd185821d42fa48608f4c5fdf9ff510ace3f..721eeb9ee083c5f0c31dc52edebd8b356ab3448b 100644
--- a/VERSION
+++ b/VERSION
@@ -90,5 +90,5 @@ IPA_DATA_VERSION=20100614120000
 #                                                      #
 ########################################################
 IPA_API_VERSION_MAJOR=2
-IPA_API_VERSION_MINOR=165
-# Last change: mbasti - limit ipamaxusernamelength value to 255
+IPA_API_VERSION_MINOR=166
+# Last change: npmccallum - enable setting authinds on services
diff --git a/ipalib/plugins/service.py b/ipalib/plugins/service.py
index 644b07f463a008eff12d3ff36de9404c03cf8a69..368f4f850c7452a9ed98ae0173a3163572d491d8 100644
--- a/ipalib/plugins/service.py
+++ b/ipalib/plugins/service.py
@@ -506,6 +506,14 @@ class service(LDAPObject):
             values=(u'MS-PAC', u'PAD', u'NONE'),
             csv=True,
         ),
+        StrEnum('krbprincipalauthind',
+            cli_name='auth_ind',
+            label=_('Authentication Indicators'),
+            doc=_('Authentication indicator whitelist'),
+            values=(u'otp', u'radius'),
+            multivalue=True,
+            required=False,
+        ),
     ) + ticket_flags_params
 
     def validate_ipakrbauthzdata(self, entry):
-- 
2.7.4

From a73f2f9898e658067c608bc11958f76d9557030d Mon Sep 17 00:00:00 2001
From: Nathaniel McCallum <npmccal...@redhat.com>
Date: Sun, 21 Feb 2016 19:44:19 -0500
Subject: [PATCH 4/5] Enable authentication indicators for OTP and RADIUS

If the user is configured for OTP or RADIUS authentication, insert the
relevant authentication indicator.
---
 daemons/ipa-kdb/ipa_kdb_principals.c | 10 ++++++++--
 1 file changed, 8 insertions(+), 2 deletions(-)

diff --git a/daemons/ipa-kdb/ipa_kdb_principals.c b/daemons/ipa-kdb/ipa_kdb_principals.c
index 910d55c4a3e3eecedc1f32eb9f724ae76f40aa57..d4adf27f2de7c7ccd050063e779a30fdae35bc83 100644
--- a/daemons/ipa-kdb/ipa_kdb_principals.c
+++ b/daemons/ipa-kdb/ipa_kdb_principals.c
@@ -512,7 +512,8 @@ static krb5_error_code ipadb_parse_ldap_entry(krb5_context kcontext,
                                               krb5_db_entry **kentry,
                                               uint32_t *polmask)
 {
-    krb5_octet otp_string[] = {'o', 't', 'p', 0, '[', ']', 0 };
+    const krb5_octet rad_string[] = "otp\0[{\"indicators\": [\"radius\"]}]";
+    const krb5_octet otp_string[] = "otp\0[{\"indicators\": [\"otp\"]}]";
     struct ipadb_context *ipactx;
     enum ipadb_user_auth ua;
     LDAP *lcontext;
@@ -842,11 +843,16 @@ static krb5_error_code ipadb_parse_ldap_entry(krb5_context kcontext,
     }
 
     /* If enabled, set the otp user string, enabling otp. */
-    if (ua & (IPADB_USER_AUTH_RADIUS | IPADB_USER_AUTH_OTP)) {
+    if (ua & IPADB_USER_AUTH_OTP) {
         kerr = ipadb_set_tl_data(entry, KRB5_TL_STRING_ATTRS,
                                  sizeof(otp_string), otp_string);
         if (kerr)
             goto done;
+    } else if (ua & IPADB_USER_AUTH_RADIUS) {
+        kerr = ipadb_set_tl_data(entry, KRB5_TL_STRING_ATTRS,
+                                 sizeof(rad_string), rad_string);
+        if (kerr)
+            goto done;
     }
 
     kerr = 0;
-- 
2.7.4

From ff2e7ec9c56c1ef8304a57f343d1d9b208bae48b Mon Sep 17 00:00:00 2001
From: Nathaniel McCallum <npmccal...@redhat.com>
Date: Sun, 21 Feb 2016 19:43:52 -0500
Subject: [PATCH 3/5] Return password-only preauth if passwords are allowed

Before this patch, if either password or password+otp were permitted,
only the otp preauth mech would be returned to the client. Now, the
client will receive either enc_ts or enc_chl in addition to otp.
---
 daemons/ipa-kdb/ipa_kdb_principals.c | 6 ++----
 1 file changed, 2 insertions(+), 4 deletions(-)

diff --git a/daemons/ipa-kdb/ipa_kdb_principals.c b/daemons/ipa-kdb/ipa_kdb_principals.c
index 50278108769c9032a277f0b53f14380ad4257566..910d55c4a3e3eecedc1f32eb9f724ae76f40aa57 100644
--- a/daemons/ipa-kdb/ipa_kdb_principals.c
+++ b/daemons/ipa-kdb/ipa_kdb_principals.c
@@ -302,6 +302,8 @@ static void ipadb_validate_radius(struct ipadb_context *ipactx,
                                "ipatokenRadiusConfigLink");
     if (vals == NULL || vals[0] == NULL)
         *ua &= ~IPADB_USER_AUTH_RADIUS;
+    else
+        *ua = IPADB_USER_AUTH_RADIUS;
 
     if (vals != NULL)
         ldap_value_free_len(vals);
@@ -314,10 +316,6 @@ static void ipadb_validate_password(struct ipadb_context *ipactx,
     /* If no mechanisms are set, use password. */
     if (*ua == IPADB_USER_AUTH_NONE)
         *ua |= IPADB_USER_AUTH_PASSWORD;
-
-    /* If any other mechanism has passed validation, don't use password. */
-    else if (*ua & ~IPADB_USER_AUTH_PASSWORD)
-        *ua &= ~IPADB_USER_AUTH_PASSWORD;
 }
 
 static enum ipadb_user_auth ipadb_get_user_auth(struct ipadb_context *ipactx,
-- 
2.7.4

From a4db4e5fb4548d1de0e3f65b720368142e2a43c6 Mon Sep 17 00:00:00 2001
From: Nathaniel McCallum <npmccal...@redhat.com>
Date: Sun, 21 Feb 2016 19:31:14 -0500
Subject: [PATCH 2/5] Validate the auth method control in ipa-otpd

If a user is configured for OTP authentication, we ensure that an OTP
token was actually validated. This guarantees that no 1FAs can occur
through the ipa-otpd codepath.

TODO: assign OID
---
 daemons/ipa-otpd/bind.c | 47 +++++++++++++++++++++++++++++++++++++++--------
 1 file changed, 39 insertions(+), 8 deletions(-)

diff --git a/daemons/ipa-otpd/bind.c b/daemons/ipa-otpd/bind.c
index c985ccd7e73c6e8182cafcef49fd9873ab3340ea..3acc28e29bf6eba968ff97a6058ec8bbfebcb6ae 100644
--- a/daemons/ipa-otpd/bind.c
+++ b/daemons/ipa-otpd/bind.c
@@ -26,6 +26,30 @@
  */
 
 #include "internal.h"
+#include <stdbool.h>
+
+#define AUTH_METHOD_OID "1.2.3.4.5" /* TODO: Proper OID needed. */
+#define LBER_UTF8STRING ((ber_tag_t) 0x0cUL)
+
+static bool ber_str_equals(struct berval *bv, ber_tag_t tag, const char *str)
+{
+    ber_tag_t t = LBER_ERROR;
+    BerElement *e = NULL;
+    char *out = NULL;
+    bool eq = false;
+
+    e = ber_init(bv);
+    if (e != NULL) {
+        t = ber_get_stringa(e, &out);
+        if (t == tag)
+            eq = strcmp(str, out) == 0;
+
+        ber_memfree(out);
+        ber_free(e, 1);
+    }
+
+    return eq;
+}
 
 static void on_bind_writable(verto_ctx *vctx, verto_ev *ev)
 {
@@ -71,9 +95,11 @@ error:
 
 static void on_bind_readable(verto_ctx *vctx, verto_ev *ev)
 {
+    struct otpd_queue_item *item = NULL;
+    LDAPControl *auth_method = NULL;
+    LDAPControl **controls = NULL;
     const char *errstr = "error";
     LDAPMessage *results;
-    struct otpd_queue_item *item = NULL;
     int i, rslt;
     (void)vctx;
 
@@ -93,15 +119,20 @@ static void on_bind_readable(verto_ctx *vctx, verto_ev *ev)
     item->msgid = -1;
 
     rslt = ldap_parse_result(verto_get_private(ev), results, &i,
-                             NULL, NULL, NULL, NULL, 0);
-    if (rslt != LDAP_SUCCESS) {
-        errstr = ldap_err2string(rslt);
+                             NULL, NULL, NULL, &controls, 1);
+    if (rslt != LDAP_SUCCESS || i != LDAP_SUCCESS) {
+        errstr = ldap_err2string(rslt != LDAP_SUCCESS ? rslt : i);
         goto error;
     }
 
-    rslt = i;
-    if (rslt != LDAP_SUCCESS) {
-        errstr = ldap_err2string(rslt);
+    auth_method = ldap_control_find(AUTH_METHOD_OID, controls, NULL);
+    if (auth_method == NULL) {
+        errstr = "auth method control missing";
+        goto error;
+    }
+
+    if (!ber_str_equals(&auth_method->ldctl_value, LBER_UTF8STRING, "otp")) {
+        errstr = "invalid auth method";
         goto error;
     }
 
@@ -119,7 +150,7 @@ error:
         otpd_log_req(item->req, "bind end: %s",
                 item->rsp != NULL ? "success" : errstr);
 
-    ldap_msgfree(results);
+    ldap_controls_free(controls);
     otpd_queue_push(&ctx.stdio.responses, item);
     verto_set_flags(ctx.stdio.writer, VERTO_EV_FLAG_PERSIST |
                                       VERTO_EV_FLAG_IO_ERROR |
-- 
2.7.4

From 896fdd39bdac4d13dd8be912f5372aedcc438ec4 Mon Sep 17 00:00:00 2001
From: Nathaniel McCallum <npmccal...@redhat.com>
Date: Fri, 19 Feb 2016 21:55:08 -0500
Subject: [PATCH 1/5] Return an LDAP control indicating the auth method

This indicates to the client what credentials were validated during
the bind operation. For example, if an OTP token was validated, the
control contains the string "otp".

TODO: assign OID
---
 daemons/ipa-slapi-plugins/ipa-pwd-extop/prepost.c | 73 +++++++++++++++++++----
 1 file changed, 63 insertions(+), 10 deletions(-)

diff --git a/daemons/ipa-slapi-plugins/ipa-pwd-extop/prepost.c b/daemons/ipa-slapi-plugins/ipa-pwd-extop/prepost.c
index c1fc7fe3345e227054b8118b35d8f504fe957ea6..ff1752ef1acdc686901f1aa2a940e1d3531923a8 100644
--- a/daemons/ipa-slapi-plugins/ipa-pwd-extop/prepost.c
+++ b/daemons/ipa-slapi-plugins/ipa-pwd-extop/prepost.c
@@ -68,6 +68,16 @@
 #define IPAPWD_OP_ADD 1
 #define IPAPWD_OP_MOD 2
 
+#define AUTH_METHOD_OID "1.2.3.4.5" /* TODO: Proper OID needed. */
+#define LBER_UTF8STRING ((ber_tag_t) 0x0cUL)
+
+enum auth_method {
+  auth_method_none = 0,
+  auth_method_sync,
+  auth_method_otp,
+  auth_method_pwd,
+} auth_method;
+
 extern Slapi_PluginDesc ipapwd_plugin_desc;
 extern void *ipapwd_plugin_id;
 extern const char *ipa_realm_tree;
@@ -1160,6 +1170,40 @@ done:
     return 0;
 }
 
+static void report_auth_method(Slapi_PBlock *pb, enum auth_method am)
+{
+    const char *name = NULL;
+    LDAPControl *c = NULL;
+    BerElement *e = NULL;
+    int rc = 0;
+
+    switch (am) {
+    case auth_method_sync: name = "otp";      break;
+    case auth_method_otp:  name = "otp";      break;
+    case auth_method_pwd:  name = "password"; break;
+    default: return;
+    }
+
+    e = ber_alloc_t(LBER_USE_DER);
+    if (e == NULL)
+        return;
+
+    rc = ber_put_string(e, name, LBER_UTF8STRING);
+    if (rc < 0) {
+        ber_free(e, 1);
+        return;
+    }
+
+    rc = slapi_build_control("", e, 0, &c);
+    ber_free(e, 1);
+    if (rc != LDAP_SUCCESS)
+        return;
+
+    rc = slapi_pblock_set(pb,  SLAPI_ADD_RESCONTROL, c);
+    if (rc != LDAP_SUCCESS)
+        ldap_control_free(c);
+}
+
 /*
  * This function handles the bind functionality for OTP. The return value
  * indicates if the OTP portion of authentication was successful.
@@ -1171,11 +1215,16 @@ done:
  *       value at the end. This leaves only the password in creds for later
  *       validation.
  */
-static bool ipapwd_pre_bind_otp(const char *bind_dn, Slapi_Entry *entry,
-                                struct berval *creds)
+static enum auth_method ipapwd_pre_bind_otp(Slapi_PBlock *pb,
+                                            Slapi_Entry *entry,
+                                            const char *bind_dn,
+                                            struct berval *creds)
 {
     uint32_t auth_types;
 
+    if (sync_request_present(pb))
+        return auth_method_sync;
+
     /* Get the configured authentication types. */
     auth_types = otp_config_auth_types(otp_config, entry);
 
@@ -1201,24 +1250,27 @@ static bool ipapwd_pre_bind_otp(const char *bind_dn, Slapi_Entry *entry,
             slapi_log_error(SLAPI_LOG_FATAL, IPAPWD_PLUGIN_NAME,
                             "%s: can't find tokens for '%s'.\n",
                             __func__, bind_dn);
-            return false;
+            return auth_method_none;
         }
 
         /* If the user has no active tokens, succeed. */
         if (tokens[0] == NULL) {
             otp_token_free_array(tokens);
-            return true;
+            return auth_method_pwd;
         }
 
         if (otp_token_validate_berval(tokens, creds, NULL)) {
             otp_token_free_array(tokens);
-            return true;
+            return auth_method_otp;
         }
 
         otp_token_free_array(tokens);
     }
 
-    return auth_types & OTP_CONFIG_AUTH_TYPE_PASSWORD;
+    if (auth_types & OTP_CONFIG_AUTH_TYPE_PASSWORD)
+        return auth_method_pwd;
+
+    return auth_method_none;
 }
 
 static int ipapwd_authenticate(const char *dn, Slapi_Entry *entry,
@@ -1393,12 +1445,12 @@ static int ipapwd_pre_bind(Slapi_PBlock *pb)
     Slapi_Entry *entry = NULL;
     char *dn = NULL;
     int method = 0;
-    bool syncreq;
     int ret = 0;
     time_t current_time;
     time_t expire_time;
     char *principal_expire = NULL;
     struct tm expire_tm;
+    enum auth_method am;
 
     /* get BIND parameters */
     ret |= slapi_pblock_get(pb, SLAPI_BIND_TARGET, &dn);
@@ -1450,8 +1502,8 @@ static int ipapwd_pre_bind(Slapi_PBlock *pb)
     }
 
     /* Try to do OTP first. */
-    syncreq = sync_request_present(pb);
-    if (!syncreq && !ipapwd_pre_bind_otp(dn, entry, credentials))
+    am = ipapwd_pre_bind_otp(pb, entry, dn, credentials);
+    if (am == auth_method_none)
         goto invalid_creds;
 
     /* Ensure that there is a password. */
@@ -1466,12 +1518,13 @@ static int ipapwd_pre_bind(Slapi_PBlock *pb)
     }
 
     /* Attempt to handle a token synchronization request. */
-    if (syncreq && !sync_request_handle(otp_config, pb, dn))
+    if (am == auth_method_sync && !sync_request_handle(otp_config, pb, dn))
         goto invalid_creds;
 
     /* Attempt to write out kerberos keys for the user. */
     ipapwd_write_krb_keys(pb, dn, entry, credentials);
 
+    report_auth_method(pb, am);
     slapi_entry_free(entry);
     return 0;
 
-- 
2.7.4

-- 
Manage your subscription for the Freeipa-devel mailing list:
https://www.redhat.com/mailman/listinfo/freeipa-devel
Contribute to FreeIPA: http://www.freeipa.org/page/Contribute/Code

Reply via email to