summaryrefslogtreecommitdiffstats
path: root/src/krb5_plugin
diff options
context:
space:
mode:
Diffstat (limited to 'src/krb5_plugin')
-rw-r--r--src/krb5_plugin/common/radius_clpreauth.c46
-rw-r--r--src/krb5_plugin/common/radius_clpreauth.h37
-rw-r--r--src/krb5_plugin/common/radius_kdcpreauth.c611
-rw-r--r--src/krb5_plugin/common/radius_kdcpreauth.h185
-rw-r--r--src/krb5_plugin/common/utils.c254
-rw-r--r--src/krb5_plugin/common/utils.h68
-rw-r--r--src/krb5_plugin/idp/idp.h70
-rw-r--r--src/krb5_plugin/idp/idp_clpreauth.c228
-rw-r--r--src/krb5_plugin/idp/idp_kdcpreauth.c406
-rw-r--r--src/krb5_plugin/idp/idp_utils.c273
-rw-r--r--src/krb5_plugin/idp/sssd_enable_idp14
-rw-r--r--src/krb5_plugin/passkey/passkey.h110
-rw-r--r--src/krb5_plugin/passkey/passkey_clpreauth.c395
-rw-r--r--src/krb5_plugin/passkey/passkey_kdcpreauth.c438
-rw-r--r--src/krb5_plugin/passkey/passkey_utils.c648
-rw-r--r--src/krb5_plugin/passkey/sssd_enable_passkey14
-rw-r--r--src/krb5_plugin/sssd_krb5_localauth_plugin.c196
-rw-r--r--src/krb5_plugin/sssd_krb5_locator_plugin.c646
18 files changed, 4639 insertions, 0 deletions
diff --git a/src/krb5_plugin/common/radius_clpreauth.c b/src/krb5_plugin/common/radius_clpreauth.c
new file mode 100644
index 0000000..b9779b2
--- /dev/null
+++ b/src/krb5_plugin/common/radius_clpreauth.c
@@ -0,0 +1,46 @@
+/*
+ Authors:
+ Pavel Březina <pbrezina@redhat.com>
+
+ Copyright (C) 2023 Red Hat
+
+ 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 <errno.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <limits.h>
+#include <krad.h>
+#include <krb5/kdcpreauth_plugin.h>
+
+#include "radius_kdcpreauth.h"
+#include "util/util.h"
+
+void
+sss_radiuscl_init(krb5_context context,
+ krb5_clpreauth_moddata moddata,
+ krb5_clpreauth_modreq *modreq_out)
+{
+ return;
+}
+
+void
+sss_radiuscl_fini(krb5_context context,
+ krb5_clpreauth_moddata moddata,
+ krb5_clpreauth_modreq modreq)
+{
+ return;
+}
diff --git a/src/krb5_plugin/common/radius_clpreauth.h b/src/krb5_plugin/common/radius_clpreauth.h
new file mode 100644
index 0000000..1dfe3df
--- /dev/null
+++ b/src/krb5_plugin/common/radius_clpreauth.h
@@ -0,0 +1,37 @@
+/*
+ Authors:
+ Pavel Březina <pbrezina@redhat.com>
+
+ Copyright (C) 2023 Red Hat
+
+ 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 _RADIUS_CLPREAUTH_H_
+#define _RADIUS_CLPREAUTH_H_
+
+#include <stdlib.h>
+#include <krb5/preauth_plugin.h>
+
+void
+sss_radiuscl_init(krb5_context context,
+ krb5_clpreauth_moddata moddata,
+ krb5_clpreauth_modreq *modreq_out);
+
+void
+sss_radiuscl_fini(krb5_context context,
+ krb5_clpreauth_moddata moddata,
+ krb5_clpreauth_modreq modreq);
+
+#endif /* _RADIUS_CLPREAUTH_H_ */
diff --git a/src/krb5_plugin/common/radius_kdcpreauth.c b/src/krb5_plugin/common/radius_kdcpreauth.c
new file mode 100644
index 0000000..77f3661
--- /dev/null
+++ b/src/krb5_plugin/common/radius_kdcpreauth.c
@@ -0,0 +1,611 @@
+/*
+ Authors:
+ Pavel Březina <pbrezina@redhat.com>
+
+ Copyright (C) 2023 Red Hat
+
+ 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 <errno.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <limits.h>
+#include <krad.h>
+#include <krb5/kdcpreauth_plugin.h>
+
+#include "krb5_plugin/common/radius_kdcpreauth.h"
+#include "krb5_plugin/common/utils.h"
+#include "util/util.h"
+
+krb5_error_code
+sss_radiuskdc_init(const char *plugin_name,
+ krb5_context kctx,
+ krb5_kdcpreauth_moddata *_moddata,
+ const char **_realmnames)
+{
+ struct sss_radiuskdc_state *state;
+
+ state = malloc(sizeof(struct sss_radiuskdc_state));
+ if (state == NULL) {
+ return ENOMEM;
+ }
+
+ state->plugin_name = plugin_name;
+
+ /* IPA is the only consumer so far so it is fine to hardcode the values. */
+ state->server = KRB5_KDC_RUNDIR "/DEFAULT.socket";
+ state->secret = "";
+ state->timeout = 5 * 1000;
+ state->retries = 3;
+
+ *_moddata = (krb5_kdcpreauth_moddata)state;
+
+ return 0;
+}
+
+void
+sss_radiuskdc_fini(krb5_context kctx,
+ krb5_kdcpreauth_moddata moddata)
+{
+ struct sss_radiuskdc_state *state;
+
+ state = (struct sss_radiuskdc_state *)moddata;
+
+ if (state == NULL) {
+ return;
+ }
+
+ free(state);
+}
+
+int
+sss_radiuskdc_flags(krb5_context kctx,
+ krb5_preauthtype pa_type)
+{
+ return PA_REPLACES_KEY;
+}
+
+krb5_error_code
+sss_radiuskdc_return_padata(krb5_context kctx,
+ krb5_pa_data *padata,
+ krb5_data *req_pkt,
+ krb5_kdc_req *request,
+ krb5_kdc_rep *reply,
+ krb5_keyblock *encrypting_key,
+ krb5_pa_data **send_pa_out,
+ krb5_kdcpreauth_callbacks cb,
+ krb5_kdcpreauth_rock rock,
+ krb5_kdcpreauth_moddata moddata,
+ krb5_kdcpreauth_modreq modreq)
+{
+ struct sss_radiuskdc_state *state;
+ krb5_keyblock *armor_key;
+ bool *result;
+
+ state = (struct sss_radiuskdc_state *)moddata;
+ result = (bool *)modreq;
+
+ /* This should not happen. */
+ if (state == NULL) {
+ return EINVAL;
+ }
+
+ /* Verification was not successful. Do not replace the key. */
+ if (result == NULL || *result == false) {
+ return 0;
+ }
+
+ /* Get the armor key. */
+ armor_key = cb->fast_armor(kctx, rock);
+ if (armor_key == NULL) {
+ com_err(state->plugin_name, ENOENT,
+ "No armor key found when returning padata");
+ return ENOENT;
+ }
+
+ /* Replace the reply key with the FAST armor key. */
+ krb5_free_keyblock_contents(kctx, encrypting_key);
+ return krb5_copy_keyblock_contents(kctx, armor_key, encrypting_key);
+}
+
+krb5_error_code
+sss_radiuskdc_enabled(const char *config_name,
+ krb5_context kctx,
+ krb5_kdcpreauth_callbacks cb,
+ krb5_kdcpreauth_rock rock,
+ char **_config)
+{
+ krb5_error_code ret;
+ char *config;
+
+ ret = cb->get_string(kctx, rock, config_name, &config);
+ if (ret != 0) {
+ return ret;
+ }
+
+ /* Disabled. */
+ if (config == NULL) {
+ return ENOENT;
+ }
+
+ /* Enabled. Return the config string. */
+ *_config = config;
+
+ return 0;
+}
+
+void
+sss_radiuskdc_config_free(struct sss_radiuskdc_config *config)
+{
+ if (config == NULL) {
+ return;
+ }
+
+ free(config->username);
+ free(config->server);
+ free(config->secret);
+ free(config);
+}
+
+krb5_error_code
+sss_radiuskdc_config_init(struct sss_radiuskdc_state *state,
+ krb5_context kctx,
+ krb5_const_principal princ,
+ const char *configstr,
+ struct sss_radiuskdc_config **_config)
+{
+ struct sss_radiuskdc_config *config;
+ krb5_error_code ret;
+ char *username;
+
+ if (state == NULL) {
+ return EINVAL;
+ }
+
+ config = malloc(sizeof(struct sss_radiuskdc_config));
+ if (config == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+ memset(config, 0, sizeof(struct sss_radiuskdc_config));
+
+ config->server = strdup(state->server);
+ config->secret = strdup(state->secret);
+ config->retries = state->retries;
+ config->timeout = state->timeout;
+
+ if (config->server == NULL || config->secret == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ ret = krb5_unparse_name_flags(kctx, princ, 0, &username);
+ if (ret != 0) {
+ goto done;
+ }
+
+ config->username = strdup(username);
+ krb5_free_unparsed_name(kctx, username);
+ if (config->username == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ *_config = config;
+ ret = 0;
+
+done:
+ if (ret != 0) {
+ sss_radiuskdc_config_free(config);
+ }
+
+ return ret;
+}
+
+krb5_error_code
+sss_radiuskdc_set_cookie(krb5_context context,
+ krb5_kdcpreauth_callbacks cb,
+ krb5_kdcpreauth_rock rock,
+ krb5_preauthtype pa_type,
+ const krb5_data *state)
+{
+ krb5_data cookie;
+ unsigned int len;
+ uint8_t *blob;
+ size_t pctr;
+
+ len = sizeof(uint16_t) + state->length;
+ blob = malloc(len);
+ if (blob == NULL) {
+ return ENOMEM;
+ }
+
+ pctr = 0;
+ SAFEALIGN_SET_UINT16(&blob[pctr], 1, &pctr);
+ SAFEALIGN_SET_STRING(&blob[pctr], state->data, state->length, &pctr);
+
+ cookie.magic = 0;
+ cookie.data = (char *)blob;
+ cookie.length = len;
+
+ return cb->set_cookie(context, rock, pa_type, &cookie);
+}
+
+krb5_error_code
+sss_radiuskdc_get_cookie(krb5_context context,
+ krb5_kdcpreauth_callbacks cb,
+ krb5_kdcpreauth_rock rock,
+ krb5_preauthtype pa_type,
+ krb5_data *_state)
+{
+ uint16_t version;
+ krb5_data cookie;
+ krb5_data state;
+ size_t pctr;
+
+ if (!cb->get_cookie(context, rock, pa_type, &cookie)) {
+ return KRB5KDC_ERR_PREAUTH_FAILED;
+ }
+
+ if (cookie.length < sizeof(uint16_t)) {
+ return EINVAL;
+ }
+
+ pctr = 0;
+ SAFEALIGN_COPY_UINT16(&version, cookie.data, &pctr);
+ state.magic = 0;
+ state.data = &cookie.data[pctr];
+ state.length = cookie.length - sizeof(uint16_t);
+
+ *_state = state;
+
+ return 0;
+}
+
+
+/* Some attributes have limited length. In order to accept longer values,
+ * we will concatenate all attribute values to single krb5_data. */
+krb5_error_code
+sss_radiuskdc_get_complete_attr(const krad_packet *rres,
+ const char *attr_name,
+ krb5_data *_data)
+{
+ krad_attr attr = krad_attr_name2num(attr_name);
+ const krb5_data *rmsg;
+ krb5_data data = {0};
+ unsigned int memindex;
+ unsigned int i;
+
+ i = 0;
+ do {
+ rmsg = krad_packet_get_attr(rres, attr, i);
+ if (rmsg != NULL) {
+ data.length += rmsg->length;
+ }
+ i++;
+ } while (rmsg != NULL);
+
+ if (data.length == 0) {
+ return ENOENT;
+ }
+
+ data.data = malloc(data.length);
+ if (data.data == NULL) {
+ return ENOMEM;
+ }
+
+ i = 0;
+ memindex = 0;
+ do {
+ rmsg = krad_packet_get_attr(rres, attr, i);
+ if (rmsg != NULL) {
+ memcpy(&data.data[memindex], rmsg->data, rmsg->length);
+ memindex += rmsg->length;
+ }
+ i++;
+ } while (rmsg != NULL);
+
+ if (memindex != data.length) {
+ free(data.data);
+ return ERANGE;
+ }
+
+ *_data = data;
+
+ return 0;
+}
+
+/* From krad internals, RFC 2865 */
+#ifndef UCHAR_MAX
+#define UCHAR_MAX 255
+#endif
+#define MAX_ATTRSIZE (UCHAR_MAX - 2)
+
+krb5_error_code
+sss_radiuskdc_put_complete_attr(krad_attrset *attrset,
+ krad_attr attr,
+ const krb5_data *datap)
+{
+ krb5_data state = {0};
+ char *p = datap->data;
+ unsigned int len = datap->length;
+ krb5_error_code ret = 0;
+
+ do {
+ /* - 5 to make sure we fit into minimal value length */
+ state.data = p;
+ state.length = MIN(MAX_ATTRSIZE - 5, len);
+ p += state.length;
+
+ ret = krad_attrset_add(attrset, attr, &(state));
+ if (ret != 0) {
+ break;
+ }
+ len -= state.length;
+ } while (len > 0);
+
+ return ret;
+}
+
+char *
+sss_radiuskdc_get_attr_as_string(const krad_packet *packet, const char *attr)
+{
+ krb5_data data = {0};
+ krb5_error_code ret;
+ char *str;
+
+ ret = sss_radiuskdc_get_complete_attr(packet, attr, &data);
+ if (ret != 0) {
+ return NULL;
+ }
+
+ str = strndup(data.data, data.length);
+ free(data.data);
+
+ return str;
+}
+
+krb5_error_code
+sss_radiuskdc_set_attr_as_string(krad_attrset *attrset,
+ const char *attr,
+ const char *value)
+{
+ krb5_data data = {0};
+ krb5_error_code ret;
+
+ data.data = discard_const(value);
+ data.length = strlen(value) + 1;
+
+ ret = sss_radiuskdc_put_complete_attr(attrset,
+ krad_attr_name2num(attr),
+ &data);
+
+ return ret;
+}
+
+void
+sss_radiuskdc_client_free(struct sss_radiuskdc_client *client)
+{
+ if (client == NULL) {
+ return;
+ }
+
+ krad_client_free(client->client);
+ krad_attrset_free(client->attrs);
+ free(client);
+}
+
+struct sss_radiuskdc_client *
+sss_radiuskdc_client_init(krb5_context kctx,
+ verto_ctx *vctx,
+ struct sss_radiuskdc_config *config)
+{
+ struct sss_radiuskdc_client *client;
+ char hostname[HOST_NAME_MAX + 1];
+ krb5_data data = {0};
+ krb5_error_code ret;
+
+ client = malloc(sizeof(struct sss_radiuskdc_client));
+ if (client == NULL) {
+ return NULL;
+ }
+ memset(client, 0, sizeof(struct sss_radiuskdc_client));
+
+ ret = krad_client_new(kctx, vctx, &client->client);
+ if (ret != 0) {
+ goto fail;
+ }
+
+ ret = krad_attrset_new(kctx, &client->attrs);
+ if (ret != 0) {
+ goto fail;
+ }
+
+ ret = gethostname(hostname, sizeof(hostname) / sizeof(char));
+ if (ret != 0) {
+ goto fail;
+ }
+
+ data.data = hostname;
+ data.length = strlen(hostname);
+ ret = krad_attrset_add(client->attrs, krad_attr_name2num("NAS-Identifier"),
+ &data);
+ if (ret != 0) {
+ goto fail;
+ }
+
+ ret = krad_attrset_add_number(client->attrs, krad_attr_name2num("Service-Type"),
+ KRAD_SERVICE_TYPE_AUTHENTICATE_ONLY);
+ if (ret != 0) {
+ goto fail;
+ }
+
+ data.data = config->username;
+ data.length = strlen(config->username);
+ ret = krad_attrset_add(client->attrs, krad_attr_name2num("User-Name"),
+ &data);
+ if (ret != 0) {
+ goto fail;
+ }
+
+ return client;
+
+fail:
+ sss_radiuskdc_client_free(client);
+ return NULL;
+}
+
+void
+sss_radiuskdc_challenge_free(struct sss_radiuskdc_challenge *state)
+{
+ if (state == NULL) {
+ return;
+ }
+
+ sss_radiuskdc_client_free(state->client);
+ free(state);
+}
+
+struct sss_radiuskdc_challenge *
+sss_radiuskdc_challenge_init(krb5_context kctx,
+ krb5_kdcpreauth_callbacks cb,
+ krb5_kdcpreauth_rock rock,
+ krb5_kdcpreauth_edata_respond_fn respond,
+ void *arg,
+ struct sss_radiuskdc_config *config)
+{
+ struct sss_radiuskdc_challenge *state;
+
+ state = malloc(sizeof(struct sss_radiuskdc_challenge));
+ if (state == NULL) {
+ return NULL;
+ }
+ memset(state, 0, sizeof(struct sss_radiuskdc_challenge));
+
+ state->kctx = kctx;
+ state->cb = cb;
+ state->rock = rock;
+ state->respond = respond;
+ state->arg = arg;
+
+ state->client = sss_radiuskdc_client_init(kctx,
+ cb->event_context(kctx, rock),
+ config);
+ if (state->client == NULL) {
+ sss_radiuskdc_challenge_free(state);
+ return NULL;
+ }
+
+ return state;
+}
+
+void
+sss_radiuskdc_verify_free(struct sss_radiuskdc_verify *state)
+{
+ if (state == NULL) {
+ return;
+ }
+
+ sss_string_array_free(state->indicators);
+ sss_radiuskdc_client_free(state->client);
+ free(state);
+}
+
+struct sss_radiuskdc_verify *
+sss_radiuskdc_verify_init(krb5_context kctx,
+ krb5_kdcpreauth_rock rock,
+ krb5_kdcpreauth_callbacks cb,
+ krb5_enc_tkt_part *enc_tkt_reply,
+ krb5_kdcpreauth_verify_respond_fn respond,
+ void *arg,
+ char **indicators,
+ struct sss_radiuskdc_config *config)
+{
+ struct sss_radiuskdc_verify *state;
+
+ state = malloc(sizeof(struct sss_radiuskdc_verify));
+ if (state == NULL) {
+ return NULL;
+ }
+ memset(state, 0, sizeof(struct sss_radiuskdc_verify));
+
+ state->kctx = kctx;
+ state->rock = rock;
+ state->cb = cb;
+ state->enc_tkt_reply = enc_tkt_reply;
+ state->respond = respond;
+ state->arg = arg;
+
+ state->indicators = sss_string_array_copy(indicators);
+ if (state->indicators == NULL) {
+ sss_radiuskdc_verify_free(state);
+ return NULL;
+ }
+
+ state->client = sss_radiuskdc_client_init(kctx,
+ cb->event_context(kctx, rock),
+ config);
+ if (state->client == NULL) {
+ sss_radiuskdc_verify_free(state);
+ return NULL;
+ }
+
+ return state;
+}
+
+void
+sss_radiuskdc_verify_done(krb5_error_code rret,
+ const krad_packet *rreq,
+ const krad_packet *rres,
+ void *data)
+{
+ static bool verify_success = true;
+ static bool verify_failure = false;
+ struct sss_radiuskdc_verify *state;
+ krb5_kdcpreauth_modreq modreq;
+ krb5_error_code ret;
+ int i;
+
+ state = (struct sss_radiuskdc_verify *)data;
+ modreq = (krb5_kdcpreauth_modreq)&verify_failure;
+
+ if (rret != 0) {
+ ret = rret;
+ goto done;
+ }
+
+ if (krad_packet_get_code(rres) != krad_code_name2num("Access-Accept")) {
+ ret = KRB5_PREAUTH_FAILED;
+ goto done;
+ }
+
+ state->enc_tkt_reply->flags |= TKT_FLG_PRE_AUTH;
+
+ for (i = 0; state->indicators[i] != NULL; i++) {
+ ret = state->cb->add_auth_indicator(state->kctx, state->rock,
+ state->indicators[i]);
+ if (ret != 0) {
+ goto done;
+ }
+ }
+
+ modreq = (krb5_kdcpreauth_modreq)&verify_success;
+ ret = 0;
+
+done:
+ state->respond(state->arg, ret, modreq, NULL, NULL);
+ sss_radiuskdc_verify_free(state);
+}
diff --git a/src/krb5_plugin/common/radius_kdcpreauth.h b/src/krb5_plugin/common/radius_kdcpreauth.h
new file mode 100644
index 0000000..7e032b3
--- /dev/null
+++ b/src/krb5_plugin/common/radius_kdcpreauth.h
@@ -0,0 +1,185 @@
+/*
+ Authors:
+ Pavel Březina <pbrezina@redhat.com>
+
+ Copyright (C) 2023 Red Hat
+
+ 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 _RADIUS_KDCPREAUTH_H_
+#define _RADIUS_KDCPREAUTH_H_
+
+#include <stdlib.h>
+#include <krb5/preauth_plugin.h>
+
+struct sss_radiuskdc_state {
+ const char *plugin_name;
+ const char *server;
+ const char *secret;
+ size_t retries;
+ int timeout;
+};
+
+struct sss_radiuskdc_config {
+ char *username;
+ char *server;
+ char *secret;
+ size_t retries;
+ int timeout;
+};
+
+struct sss_radiuskdc_client {
+ krad_client *client;
+ krad_attrset *attrs;
+};
+
+struct sss_radiuskdc_challenge {
+ struct sss_radiuskdc_client *client;
+
+ krb5_context kctx;
+ krb5_kdcpreauth_callbacks cb;
+ krb5_kdcpreauth_rock rock;
+ krb5_kdcpreauth_edata_respond_fn respond;
+ void *arg;
+};
+
+struct sss_radiuskdc_verify {
+ struct sss_radiuskdc_client *client;
+ char **indicators;
+
+ krb5_context kctx;
+ krb5_kdcpreauth_rock rock;
+ krb5_kdcpreauth_callbacks cb;
+ krb5_enc_tkt_part *enc_tkt_reply;
+ krb5_kdcpreauth_verify_respond_fn respond;
+ void *arg;
+};
+
+krb5_error_code
+sss_radiuskdc_init(const char *plugin_name,
+ krb5_context kctx,
+ krb5_kdcpreauth_moddata *_moddata,
+ const char **_realmnames);
+
+void
+sss_radiuskdc_fini(krb5_context kctx,
+ krb5_kdcpreauth_moddata moddata);
+
+int
+sss_radiuskdc_flags(krb5_context kctx,
+ krb5_preauthtype pa_type);
+
+krb5_error_code
+sss_radiuskdc_return_padata(krb5_context kctx,
+ krb5_pa_data *padata,
+ krb5_data *req_pkt,
+ krb5_kdc_req *request,
+ krb5_kdc_rep *reply,
+ krb5_keyblock *encrypting_key,
+ krb5_pa_data **send_pa_out,
+ krb5_kdcpreauth_callbacks cb,
+ krb5_kdcpreauth_rock rock,
+ krb5_kdcpreauth_moddata moddata,
+ krb5_kdcpreauth_modreq modreq);
+
+krb5_error_code
+sss_radiuskdc_enabled(const char *config_name,
+ krb5_context kctx,
+ krb5_kdcpreauth_callbacks cb,
+ krb5_kdcpreauth_rock rock,
+ char **_config);
+
+void
+sss_radiuskdc_config_free(struct sss_radiuskdc_config *config);
+
+krb5_error_code
+sss_radiuskdc_config_init(struct sss_radiuskdc_state *state,
+ krb5_context kctx,
+ krb5_const_principal princ,
+ const char *configstr,
+ struct sss_radiuskdc_config **_config);
+
+krb5_error_code
+sss_radiuskdc_set_cookie(krb5_context context,
+ krb5_kdcpreauth_callbacks cb,
+ krb5_kdcpreauth_rock rock,
+ krb5_preauthtype pa_type,
+ const krb5_data *state);
+
+krb5_error_code
+sss_radiuskdc_get_cookie(krb5_context context,
+ krb5_kdcpreauth_callbacks cb,
+ krb5_kdcpreauth_rock rock,
+ krb5_preauthtype pa_type,
+ krb5_data *_state);
+
+krb5_error_code
+sss_radiuskdc_get_complete_attr(const krad_packet *rres,
+ const char *attr_name,
+ krb5_data *_data);
+
+krb5_error_code
+sss_radiuskdc_put_complete_attr(krad_attrset *attrset,
+ krad_attr attr,
+ const krb5_data *datap);
+
+char *
+sss_radiuskdc_get_attr_as_string(const krad_packet *packet, const char *attr);
+
+
+krb5_error_code
+sss_radiuskdc_set_attr_as_string(krad_attrset *attrset,
+ const char *attr,
+ const char *value);
+
+void
+sss_radiuskdc_client_free(struct sss_radiuskdc_client *client);
+
+struct sss_radiuskdc_client *
+sss_radiuskdc_client_init(krb5_context kctx,
+ verto_ctx *vctx,
+ struct sss_radiuskdc_config *config);
+
+void
+sss_radiuskdc_challenge_free(struct sss_radiuskdc_challenge *state);
+
+struct sss_radiuskdc_challenge *
+sss_radiuskdc_challenge_init(krb5_context kctx,
+ krb5_kdcpreauth_callbacks cb,
+ krb5_kdcpreauth_rock rock,
+ krb5_kdcpreauth_edata_respond_fn respond,
+ void *arg,
+ struct sss_radiuskdc_config *config);
+
+void
+sss_radiuskdc_verify_free(struct sss_radiuskdc_verify *state);
+
+struct sss_radiuskdc_verify *
+sss_radiuskdc_verify_init(krb5_context kctx,
+ krb5_kdcpreauth_rock rock,
+ krb5_kdcpreauth_callbacks cb,
+ krb5_enc_tkt_part *enc_tkt_reply,
+ krb5_kdcpreauth_verify_respond_fn respond,
+ void *arg,
+ char **indicators,
+ struct sss_radiuskdc_config *config);
+
+void
+sss_radiuskdc_verify_done(krb5_error_code rret,
+ const krad_packet *rreq,
+ const krad_packet *rres,
+ void *data);
+
+#endif /* _RADIUS_KDCPREAUTH_H_ */
diff --git a/src/krb5_plugin/common/utils.c b/src/krb5_plugin/common/utils.c
new file mode 100644
index 0000000..2a0ecc0
--- /dev/null
+++ b/src/krb5_plugin/common/utils.c
@@ -0,0 +1,254 @@
+/*
+ Authors:
+ Pavel Březina <pbrezina@redhat.com>
+
+ Copyright (C) 2023 Red Hat
+
+ 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 "config.h"
+
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <jansson.h>
+#include <krb5/preauth_plugin.h>
+
+#include "krb5_plugin/common/utils.h"
+
+void
+sss_string_array_free(char **array)
+{
+ size_t i;
+
+ if (array == NULL) {
+ return;
+ }
+
+ for (i = 0; array[i] != NULL; i++) {
+ free(array[i]);
+ }
+
+ free(array);
+}
+
+char **
+sss_string_array_copy(char **array)
+{
+ char **copy;
+ size_t i;
+
+ for (i = 0; array[i] != NULL; i++) {
+ /* Just count. */
+ }
+
+ copy = calloc(i + 1, sizeof(char *));
+ if (copy == NULL) {
+ return NULL;
+ }
+
+ for (i = 0; array[i] != NULL; i++) {
+ copy[i] = strdup(array[i]);
+ if (copy[i] == NULL) {
+ sss_string_array_free(copy);
+ return NULL;
+ }
+ }
+
+ copy[i] = NULL;
+
+ return copy;
+}
+
+char **
+sss_json_array_to_strings(json_t *jarray)
+{
+ const char *strval;
+ char **array;
+ json_t *jval;
+ size_t i;
+
+ if (!json_is_array(jarray)) {
+ return NULL;
+ }
+
+ array = calloc(json_array_size(jarray) + 1, sizeof(char *));
+ if (array == NULL) {
+ return NULL;
+ }
+
+ json_array_foreach(jarray, i, jval) {
+ strval = json_string_value(jval);
+ if (strval == NULL) {
+ goto fail;
+ }
+
+ array[i] = strdup(strval);
+ if (array[i] == NULL) {
+ goto fail;
+ }
+ }
+
+ return array;
+
+fail:
+ sss_string_array_free(array);
+
+ return NULL;
+}
+
+json_t *
+sss_strings_to_json_array(char **array)
+{
+ json_t *jarray;
+ json_t *jstr;
+ size_t i;
+ int jret;
+
+ jarray = json_array();
+ if (jarray == NULL) {
+ return NULL;
+ }
+
+ if (array == NULL) {
+ return jarray;
+ }
+
+ for (i = 0; array[i] != NULL; i++) {
+ jstr = json_string(array[i]);
+ if (jstr == NULL) {
+ goto fail;
+ }
+
+ jret = json_array_append_new(jarray, jstr);
+ if (jret != 0) {
+ goto fail;
+ }
+ }
+
+ return jarray;
+
+fail:
+ json_decref(jarray);
+
+ return NULL;
+}
+
+void *
+sss_radius_message_decode(const char *prefix,
+ sss_radius_message_decode_fn fn,
+ const char *str)
+{
+ size_t prefix_len;
+
+ if (str == NULL) {
+ return NULL;
+ }
+
+ prefix_len = strlen(prefix);
+ if (strncmp(str, prefix, prefix_len) != 0) {
+ return NULL;
+ }
+
+ return fn(str + prefix_len);
+}
+
+char *
+sss_radius_message_encode(const char *prefix,
+ sss_radius_message_encode_fn fn,
+ const void *data)
+{
+ char *json_str;
+ char *str;
+ int aret;
+
+ json_str = fn(data);
+ if (json_str == NULL) {
+ return NULL;
+ }
+
+ aret = asprintf(&str, "%s%s", prefix, json_str);
+ free(json_str);
+ if (aret < 0) {
+ return NULL;
+ }
+
+ return str;
+}
+
+krb5_pa_data *
+sss_radius_encode_padata(krb5_preauthtype patype,
+ sss_radius_message_encode_fn fn,
+ const void *data)
+{
+ krb5_pa_data *padata;
+ char *str;
+
+ str = fn(data);
+ if (str == NULL) {
+ return NULL;
+ }
+
+ padata = malloc(sizeof(krb5_pa_data));
+ if (padata == NULL) {
+ free(str);
+ return NULL;
+ }
+
+ padata->pa_type = patype;
+ padata->contents = (krb5_octet*)str;
+ padata->length = strlen(str) + 1;
+
+ return padata;
+}
+
+void *
+sss_radius_decode_padata(sss_radius_message_decode_fn fn,
+ krb5_pa_data *padata)
+{
+ if (padata->length == 0 || padata->contents == NULL) {
+ return NULL;
+ }
+
+ /* contents is NULL terminated string */
+ if (padata->contents[padata->length - 1] != '\0') {
+ return NULL;
+ }
+
+ return fn((const char*)padata->contents);
+}
+
+krb5_pa_data **
+sss_radius_encode_padata_array(krb5_preauthtype patype,
+ sss_radius_message_encode_fn fn,
+ const void *data)
+{
+ krb5_pa_data **array;
+
+ array = calloc(2, sizeof(krb5_pa_data *));
+ if (array == NULL) {
+ return NULL;
+ }
+
+ array[0] = sss_radius_encode_padata(patype, fn, data);
+ array[1] = NULL;
+
+ if (array[0] == NULL) {
+ free(array);
+ return NULL;
+ }
+
+ return array;
+}
diff --git a/src/krb5_plugin/common/utils.h b/src/krb5_plugin/common/utils.h
new file mode 100644
index 0000000..b7042c6
--- /dev/null
+++ b/src/krb5_plugin/common/utils.h
@@ -0,0 +1,68 @@
+/*
+ Authors:
+ Pavel Březina <pbrezina@redhat.com>
+
+ Copyright (C) 2023 Red Hat
+
+ 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 _KRB5_PLUGIN_UTILS_H_
+#define _KRB5_PLUGIN_UTILS_H_
+
+#include <jansson.h>
+#include <krb5/preauth_plugin.h>
+
+#define is_empty(var) ((var) == NULL || (var)[0] == '\0')
+
+void
+sss_string_array_free(char **array);
+
+char **
+sss_string_array_copy(char **array);
+
+char **
+sss_json_array_to_strings(json_t *jarray);
+
+json_t *
+sss_strings_to_json_array(char **array);
+
+typedef void * (*sss_radius_message_decode_fn)(const char *);
+typedef char * (*sss_radius_message_encode_fn)(const void *);
+
+void *
+sss_radius_message_decode(const char *prefix,
+ sss_radius_message_decode_fn fn,
+ const char *str);
+
+char *
+sss_radius_message_encode(const char *prefix,
+ sss_radius_message_encode_fn fn,
+ const void *data);
+
+krb5_pa_data *
+sss_radius_encode_padata(krb5_preauthtype patype,
+ sss_radius_message_encode_fn fn,
+ const void *data);
+
+void *
+sss_radius_decode_padata(sss_radius_message_decode_fn fn,
+ krb5_pa_data *padata);
+
+krb5_pa_data **
+sss_radius_encode_padata_array(krb5_preauthtype patype,
+ sss_radius_message_encode_fn fn,
+ const void *data);
+
+#endif /* _KRB5_PLUGIN_UTILS_H_ */
diff --git a/src/krb5_plugin/idp/idp.h b/src/krb5_plugin/idp/idp.h
new file mode 100644
index 0000000..c964580
--- /dev/null
+++ b/src/krb5_plugin/idp/idp.h
@@ -0,0 +1,70 @@
+/*
+ Authors:
+ Pavel Březina <pbrezina@redhat.com>
+
+ Copyright (C) 2021 Red Hat
+
+ 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 _IDP_H_
+#define _IDP_H_
+
+#include <stdlib.h>
+#include <krb5/preauth_plugin.h>
+
+#ifndef discard_const
+#define discard_const(ptr) ((void *)((uintptr_t)(ptr)))
+#endif
+
+#define SSSD_IDP_PLUGIN "idp"
+#define SSSD_IDP_CONFIG "idp"
+#define SSSD_IDP_OAUTH2_PADATA 152 // PA-REDHAT-IDP-OAUTH2
+#define SSSD_IDP_OAUTH2_QUESTION "idp-oauth2"
+#define SSSD_IDP_OAUTH2_PREFIX "oauth2 "
+
+struct sss_idp_config {
+ char *type;
+ char **indicators;
+};
+
+void
+sss_idp_config_free(struct sss_idp_config *idpcfg);
+
+krb5_error_code
+sss_idp_config_init(const char *config,
+ struct sss_idp_config **_idpcfg);
+
+struct sss_idp_oauth2 {
+ char *verification_uri;
+ char *verification_uri_complete;
+ char *user_code;
+};
+
+void
+sss_idp_oauth2_free(struct sss_idp_oauth2 *data);
+
+krb5_pa_data *
+sss_idp_oauth2_encode_padata(struct sss_idp_oauth2 *data);
+
+struct sss_idp_oauth2 *
+sss_idp_oauth2_decode_padata(krb5_pa_data *padata);
+
+char *
+sss_idp_oauth2_encode_challenge(struct sss_idp_oauth2 *data);
+
+struct sss_idp_oauth2 *
+sss_idp_oauth2_decode_challenge(const char *str);
+
+#endif /* _IDP_H_ */
diff --git a/src/krb5_plugin/idp/idp_clpreauth.c b/src/krb5_plugin/idp/idp_clpreauth.c
new file mode 100644
index 0000000..91fe346
--- /dev/null
+++ b/src/krb5_plugin/idp/idp_clpreauth.c
@@ -0,0 +1,228 @@
+/*
+ Authors:
+ Pavel Březina <pbrezina@redhat.com>
+
+ Copyright (C) 2021 Red Hat
+
+ 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 "config.h"
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <krb5/clpreauth_plugin.h>
+
+#include "krb5_plugin/common/radius_clpreauth.h"
+#include "idp.h"
+
+static krb5_pa_data **
+sss_idpcl_encode_padata(void)
+{
+ krb5_pa_data **padata;
+
+ padata = calloc(2, sizeof(krb5_pa_data *));
+ if (padata == NULL) {
+ return NULL;
+ }
+
+ padata[0] = malloc(sizeof(krb5_pa_data));
+ if (padata[0] == NULL) {
+ free(padata);
+ return NULL;
+ }
+
+ padata[0]->pa_type = SSSD_IDP_OAUTH2_PADATA;
+ padata[0]->contents = NULL;
+ padata[0]->length = 0;
+
+ padata[1] = NULL;
+
+ return padata;
+}
+
+static krb5_error_code
+sss_idpcl_prompt(krb5_context context,
+ krb5_prompter_fct prompter,
+ void *prompter_data,
+ struct sss_idp_oauth2 *data,
+ krb5_data *_reply)
+{
+ krb5_error_code ret;
+ krb5_prompt prompt;
+ char *prompt_str;
+ int aret;
+
+ if (data->verification_uri_complete != NULL) {
+ aret = asprintf(&prompt_str,
+ "Authenticate at %1$s and press ENTER.",
+ data->verification_uri_complete);
+ } else {
+ aret = asprintf(&prompt_str,
+ "Authenticate with PIN %1$s at %2$s and press ENTER.",
+ data->user_code, data->verification_uri);
+ }
+
+ if (aret < 0) {
+ return ENOMEM;
+ }
+
+ prompt.reply = _reply;
+ prompt.prompt = prompt_str;
+ prompt.hidden = 1;
+
+ ret = (*prompter)(context, prompter_data, NULL, NULL, 1, &prompt);
+ free(prompt_str);
+
+ return ret;
+}
+
+static krb5_error_code
+sss_idpcl_prep_questions(krb5_context context,
+ krb5_clpreauth_moddata moddata,
+ krb5_clpreauth_modreq modreq,
+ krb5_get_init_creds_opt *opt,
+ krb5_clpreauth_callbacks cb,
+ krb5_clpreauth_rock rock,
+ krb5_kdc_req *request,
+ krb5_data *encoded_request_body,
+ krb5_data *encoded_previous_request,
+ krb5_pa_data *pa_data)
+{
+ struct sss_idp_oauth2 *data;
+ char *challenge = NULL;
+ krb5_error_code ret;
+
+ data = sss_idp_oauth2_decode_padata(pa_data);
+ if (data == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ challenge = sss_idp_oauth2_encode_challenge(data);
+ if (challenge == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ ret = cb->ask_responder_question(context, rock, SSSD_IDP_OAUTH2_QUESTION,
+ challenge);
+
+done:
+ sss_idp_oauth2_free(data);
+ free(challenge);
+
+ return ret;
+}
+
+static krb5_error_code
+sss_idpcl_process(krb5_context context,
+ krb5_clpreauth_moddata moddata,
+ krb5_clpreauth_modreq modreq,
+ krb5_get_init_creds_opt *opt,
+ krb5_clpreauth_callbacks cb,
+ krb5_clpreauth_rock rock,
+ krb5_kdc_req *request,
+ krb5_data *encoded_request_body,
+ krb5_data *encoded_previous_request,
+ krb5_pa_data *pa_data,
+ krb5_prompter_fct prompter,
+ void *prompter_data,
+ krb5_pa_data ***_pa_data_out)
+{
+ krb5_keyblock *as_key;
+ krb5_pa_data **padata;
+ krb5_error_code ret;
+ krb5_data reply;
+ struct sss_idp_oauth2 *data = NULL;
+ char prompt_answer[255] = {0};
+ const char *answer;
+
+ data = sss_idp_oauth2_decode_padata(pa_data);
+ if (data == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ /* Get FAST armor key. */
+ as_key = cb->fast_armor(context, rock);
+ if (as_key == NULL) {
+ ret = ENOENT;
+ goto done;
+ }
+
+ answer = cb->get_responder_answer(context, rock, SSSD_IDP_OAUTH2_QUESTION);
+ /* Call prompter if we have no answer. We don't really require any answer,
+ * but we need to present a prompt to the user and wait until the user has
+ * finished authentication via an idp provider. */
+ if (answer == NULL) {
+ reply.magic = 0;
+ reply.length = sizeof(prompt_answer) / sizeof(char);
+ reply.data = prompt_answer;
+
+ ret = sss_idpcl_prompt(context, prompter, prompter_data, data, &reply);
+ if (ret != 0) {
+ goto done;
+ }
+ }
+
+ /* Use FAST armor key as response key. */
+ ret = cb->set_as_key(context, rock, as_key);
+ if (ret != 0) {
+ goto done;
+ }
+
+ /* Encode the request into the pa_data output. */
+ padata = sss_idpcl_encode_padata();
+ if (padata == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ cb->disable_fallback(context, rock);
+ *_pa_data_out = padata;
+
+ ret = 0;
+
+done:
+ sss_idp_oauth2_free(data);
+ return ret;
+}
+
+krb5_error_code
+clpreauth_idp_initvt(krb5_context context,
+ int maj_ver,
+ int min_ver,
+ krb5_plugin_vtable vtable)
+{
+ static krb5_preauthtype pa_type_list[] = { SSSD_IDP_OAUTH2_PADATA, 0 };
+ krb5_clpreauth_vtable vt;
+
+ if (maj_ver != 1) {
+ return KRB5_PLUGIN_VER_NOTSUPP;
+ }
+
+ vt = (krb5_clpreauth_vtable)vtable;
+ vt->name = discard_const(SSSD_IDP_PLUGIN);
+ vt->pa_type_list = pa_type_list;
+ vt->request_init = sss_radiuscl_init;
+ vt->prep_questions = sss_idpcl_prep_questions;
+ vt->process = sss_idpcl_process;
+ vt->request_fini = sss_radiuscl_fini;
+ vt->gic_opts = NULL;
+
+ return 0;
+}
diff --git a/src/krb5_plugin/idp/idp_kdcpreauth.c b/src/krb5_plugin/idp/idp_kdcpreauth.c
new file mode 100644
index 0000000..7d6d032
--- /dev/null
+++ b/src/krb5_plugin/idp/idp_kdcpreauth.c
@@ -0,0 +1,406 @@
+/*
+ Authors:
+ Pavel Březina <pbrezina@redhat.com>
+
+ Copyright (C) 2021 Red Hat
+
+ 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 "config.h"
+
+#include <errno.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <limits.h>
+#include <krad.h>
+#include <krb5/kdcpreauth_plugin.h>
+
+#include "shared/safealign.h"
+#include "krb5_plugin/common/radius_kdcpreauth.h"
+#include "krb5_plugin/common/utils.h"
+#include "idp.h"
+#include "util/util.h"
+
+struct sss_idpkdc_config {
+ struct sss_radiuskdc_config *radius;
+ struct sss_idp_config *idpcfg;
+};
+
+static void
+sss_idpkdc_config_free(struct sss_idpkdc_config *config)
+{
+ if (config == NULL) {
+ return;
+ }
+
+ sss_radiuskdc_config_free(config->radius);
+ sss_idp_config_free(config->idpcfg);
+ free(config);
+}
+
+static krb5_error_code
+sss_idpkdc_config_init(struct sss_radiuskdc_state *state,
+ krb5_context kctx,
+ krb5_const_principal princ,
+ const char *configstr,
+ struct sss_idpkdc_config **_config)
+{
+ struct sss_idpkdc_config *config;
+ krb5_error_code ret;
+
+ if (state == NULL) {
+ return EINVAL;
+ }
+
+ config = malloc(sizeof(struct sss_idpkdc_config));
+ if (config == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+ memset(config, 0, sizeof(struct sss_idpkdc_config));
+
+ ret = sss_radiuskdc_config_init(state, kctx, princ, configstr, &config->radius);
+ if (ret != 0) {
+ goto done;
+ }
+
+ ret = sss_idp_config_init(configstr, &config->idpcfg);
+ if (ret != 0) {
+ goto done;
+ }
+
+ *_config = config;
+ ret = 0;
+
+done:
+ if (ret != 0) {
+ sss_idpkdc_config_free(config);
+ }
+
+ return ret;
+}
+
+static void
+sss_idpkdc_challenge_done(krb5_error_code rret,
+ const krad_packet *rreq,
+ const krad_packet *rres,
+ void *data)
+{
+ struct sss_radiuskdc_challenge *state;
+ struct sss_idp_oauth2 *idp_oauth2 = NULL;
+ krb5_pa_data *padata = NULL;
+ krb5_data rstate = {0};
+ char *rmsg = NULL;
+ krb5_error_code ret;
+
+ state = (struct sss_radiuskdc_challenge *)data;
+
+ if (rret != 0) {
+ ret = rret;
+ goto done;
+ }
+
+ if (krad_packet_get_code(rres) != krad_code_name2num("Access-Challenge")) {
+ ret = ENOENT;
+ goto done;
+ }
+
+ ret = sss_radiuskdc_get_complete_attr(rres, "Proxy-State", &rstate);
+ if (ret != 0) {
+ goto done;
+ }
+
+ rmsg = sss_radiuskdc_get_attr_as_string(rres, "Reply-Message");
+ if (rmsg == NULL) {
+ ret = EINVAL;
+ goto done;
+ }
+
+ /* Remember the RADIUS state so it can be set in the Access-Request message
+ * sent in sss_idpkdc_verify(), thus allowing the RADIUS server to
+ * associate the message with its internal state. */
+ ret = sss_radiuskdc_set_cookie(state->kctx, state->cb, state->rock,
+ SSSD_IDP_OAUTH2_PADATA, &rstate);
+ if (ret != 0) {
+ goto done;
+ }
+
+ idp_oauth2 = sss_idp_oauth2_decode_challenge(rmsg);
+ if (idp_oauth2 == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ padata = sss_idp_oauth2_encode_padata(idp_oauth2);
+ if (padata == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ ret = 0;
+
+done:
+ state->respond(state->arg, ret, padata);
+ sss_radiuskdc_challenge_free(state);
+ sss_idp_oauth2_free(idp_oauth2);
+ free(rstate.data);
+ free(rmsg);
+
+ /* padata should not be freed */
+
+ return;
+}
+
+/* Send a password-less Access-Request and expect Access-Challenge response. */
+static krb5_error_code
+sss_idpkdc_challenge_send(krb5_context kctx,
+ krb5_kdcpreauth_callbacks cb,
+ krb5_kdcpreauth_rock rock,
+ krb5_kdcpreauth_edata_respond_fn respond,
+ void *arg,
+ struct sss_radiuskdc_config *config)
+{
+ struct sss_radiuskdc_challenge *state;
+ krb5_error_code ret;
+
+ state = sss_radiuskdc_challenge_init(kctx, cb, rock, respond, arg, config);
+ if (state == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ ret = krad_client_send(state->client->client,
+ krad_code_name2num("Access-Request"),
+ state->client->attrs, config->server,
+ config->secret, config->timeout, config->retries,
+ sss_idpkdc_challenge_done, state);
+
+done:
+ if (ret != 0) {
+ sss_radiuskdc_challenge_free(state);
+ }
+
+ return ret;
+}
+
+/* Send Access-Request with password and state set to indicate that the user has
+ * finished authentication against idp provider. We expect Access-Accept. */
+static krb5_error_code
+sss_idpkdc_verify_send(krb5_context kctx,
+ krb5_kdcpreauth_rock rock,
+ krb5_kdcpreauth_callbacks cb,
+ krb5_enc_tkt_part *enc_tkt_reply,
+ krb5_kdcpreauth_verify_respond_fn respond,
+ void *arg,
+ const krb5_data *rstate,
+ char **indicators,
+ struct sss_radiuskdc_config *config)
+{
+ struct sss_radiuskdc_verify *state;
+ krb5_error_code ret;
+
+ state = sss_radiuskdc_verify_init(kctx, rock, cb, enc_tkt_reply, respond,
+ arg, indicators, config);
+ if (state == NULL) {
+ return ENOMEM;
+ }
+
+ ret = sss_radiuskdc_put_complete_attr(state->client->attrs,
+ krad_attr_name2num("Proxy-State"),
+ rstate);
+ if (ret != 0) {
+ goto done;
+ }
+
+ ret = krad_client_send(state->client->client,
+ krad_code_name2num("Access-Request"),
+ state->client->attrs, config->server,
+ config->secret, config->timeout, config->retries,
+ sss_radiuskdc_verify_done, state);
+
+done:
+ if (ret != 0) {
+ sss_radiuskdc_verify_free(state);
+ }
+
+ return ret;
+}
+
+static krb5_error_code
+sss_idpkdc_init(krb5_context kctx,
+ krb5_kdcpreauth_moddata *_moddata,
+ const char **_realmnames)
+{
+ return sss_radiuskdc_init(SSSD_IDP_PLUGIN, kctx, _moddata, _realmnames);
+}
+
+static void
+sss_idpkdc_edata(krb5_context kctx,
+ krb5_kdc_req *request,
+ krb5_kdcpreauth_callbacks cb,
+ krb5_kdcpreauth_rock rock,
+ krb5_kdcpreauth_moddata moddata,
+ krb5_preauthtype pa_type,
+ krb5_kdcpreauth_edata_respond_fn respond,
+ void *arg)
+{
+ struct sss_idpkdc_config *config = NULL;
+ struct sss_radiuskdc_state *state;
+ krb5_keyblock *armor_key;
+ char *configstr = NULL;
+ krb5_error_code ret;
+
+ state = (struct sss_radiuskdc_state *)moddata;
+
+ ret = sss_radiuskdc_enabled(SSSD_IDP_CONFIG, kctx, cb, rock, &configstr);
+ if (ret != 0) {
+ goto done;
+ }
+
+ armor_key = cb->fast_armor(kctx, rock);
+ if (armor_key == NULL) {
+ ret = ENOENT;
+ goto done;
+ }
+
+ ret = sss_idpkdc_config_init(state, kctx, cb->client_name(kctx, rock),
+ configstr, &config);
+ if (ret != 0) {
+ goto done;
+ }
+
+ ret = sss_idpkdc_challenge_send(kctx, cb, rock, respond, arg,
+ config->radius);
+
+done:
+ if (ret != 0) {
+ respond(arg, ret, NULL);
+ }
+
+ cb->free_string(kctx, rock, configstr);
+ sss_idpkdc_config_free(config);
+}
+
+static void
+sss_idpkdc_verify(krb5_context kctx,
+ krb5_data *req_pkt,
+ krb5_kdc_req *request,
+ krb5_enc_tkt_part *enc_tkt_reply,
+ krb5_pa_data *pa,
+ krb5_kdcpreauth_callbacks cb,
+ krb5_kdcpreauth_rock rock,
+ krb5_kdcpreauth_moddata moddata,
+ krb5_kdcpreauth_verify_respond_fn respond,
+ void *arg)
+{
+ struct sss_radiuskdc_state *state;
+ struct sss_idpkdc_config *config = NULL;
+ char *configstr = NULL;
+ krb5_error_code ret;
+ krb5_data rstate;
+
+ state = (struct sss_radiuskdc_state *)moddata;
+
+ ret = sss_radiuskdc_enabled(SSSD_IDP_CONFIG, kctx, cb, rock, &configstr);
+ if (ret != 0) {
+ goto done;
+ }
+
+ ret = sss_idpkdc_config_init(state, kctx, cb->client_name(kctx, rock),
+ configstr, &config);
+ if (ret != 0) {
+ goto done;
+ }
+
+ ret = sss_radiuskdc_get_cookie(kctx, cb, rock, SSSD_IDP_OAUTH2_PADATA,
+ &rstate);
+ if (ret != 0) {
+ goto done;
+ }
+
+ if (pa->pa_type != SSSD_IDP_OAUTH2_PADATA || pa->length != 0) {
+ ret = KRB5_PREAUTH_BAD_TYPE;
+ goto done;
+ }
+
+ /* config is freed by verify_done if ret == 0 */
+ ret = sss_idpkdc_verify_send(kctx, rock, cb, enc_tkt_reply, respond, arg,
+ &rstate, config->idpcfg->indicators, config->radius);
+ if (ret != 0) {
+ goto done;
+ }
+
+ ret = 0;
+
+done:
+ if (ret != 0) {
+ respond(arg, ret, NULL, NULL, NULL);
+ }
+
+ cb->free_string(kctx, rock, configstr);
+ sss_idpkdc_config_free(config);
+}
+
+krb5_error_code
+sss_idpkdc_return_padata(krb5_context kctx,
+ krb5_pa_data *padata,
+ krb5_data *req_pkt,
+ krb5_kdc_req *request,
+ krb5_kdc_rep *reply,
+ krb5_keyblock *encrypting_key,
+ krb5_pa_data **send_pa_out,
+ krb5_kdcpreauth_callbacks cb,
+ krb5_kdcpreauth_rock rock,
+ krb5_kdcpreauth_moddata moddata,
+ krb5_kdcpreauth_modreq modreq)
+{
+ /* Unexpected padata. Return error. */
+ if (padata->length != 0) {
+ return EINVAL;
+ }
+
+ return sss_radiuskdc_return_padata(kctx, padata, req_pkt, request, reply,
+ encrypting_key, send_pa_out, cb, rock,
+ moddata, modreq);
+}
+
+krb5_error_code
+kdcpreauth_idp_initvt(krb5_context kctx,
+ int maj_ver,
+ int min_ver,
+ krb5_plugin_vtable vtable)
+{
+ static krb5_preauthtype pa_type_list[] = { SSSD_IDP_OAUTH2_PADATA, 0 };
+ krb5_kdcpreauth_vtable vt;
+
+ if (maj_ver != 1) {
+ return KRB5_PLUGIN_VER_NOTSUPP;
+ }
+
+ vt = (krb5_kdcpreauth_vtable)vtable;
+ vt->name = discard_const(SSSD_IDP_PLUGIN);
+ vt->pa_type_list = pa_type_list;
+ vt->init = sss_idpkdc_init;
+ vt->fini = sss_radiuskdc_fini;
+ vt->flags = sss_radiuskdc_flags;
+ vt->edata = sss_idpkdc_edata;
+ vt->verify = sss_idpkdc_verify;
+ vt->return_padata = sss_idpkdc_return_padata;
+
+ com_err(SSSD_IDP_PLUGIN, 0, "SSSD IdP plugin loaded");
+
+ return 0;
+}
diff --git a/src/krb5_plugin/idp/idp_utils.c b/src/krb5_plugin/idp/idp_utils.c
new file mode 100644
index 0000000..90424d0
--- /dev/null
+++ b/src/krb5_plugin/idp/idp_utils.c
@@ -0,0 +1,273 @@
+/*
+ Authors:
+ Pavel Březina <pbrezina@redhat.com>
+
+ Copyright (C) 2021 Red Hat
+
+ 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 "config.h"
+
+#include <errno.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <jansson.h>
+#include <arpa/inet.h>
+#include <krb5/preauth_plugin.h>
+
+#include "krb5_plugin/common/utils.h"
+#include "krb5_plugin/idp/idp.h"
+
+void
+sss_idp_config_free(struct sss_idp_config *idpcfg)
+{
+ if (idpcfg == NULL) {
+ return;
+ }
+
+ if (idpcfg->type != NULL) {
+ free(idpcfg->type);
+ }
+
+ sss_string_array_free(idpcfg->indicators);
+ free(idpcfg);
+}
+
+/**
+ * [{
+ * "type": "oauth2",
+ * "indicators": ["..."] (optional)
+ * }]
+ *
+ * Note: array and type is used for future extensibility.
+ */
+krb5_error_code
+sss_idp_config_init(const char *config,
+ struct sss_idp_config **_idpcfg)
+{
+ struct sss_idp_config *idpcfg;
+ json_t *jindicators = NULL;
+ json_error_t jret;
+ json_t *jroot;
+ krb5_error_code ret;
+
+ idpcfg = malloc(sizeof(struct sss_idp_config));
+ if (idpcfg == NULL) {
+ return ENOMEM;
+ }
+ memset(idpcfg, 0, sizeof(struct sss_idp_config));
+
+ jroot = json_loads(config, 0, &jret);
+ if (jroot == NULL) {
+ ret = EINVAL;
+ goto done;
+ }
+
+ /* Only one item is supported at the moment. The rest is ignored. */
+ ret = json_unpack(jroot, "[{s:s, s?:o}]",
+ "type", &idpcfg->type,
+ "indicators", &jindicators);
+ if (ret != 0) {
+ ret = EINVAL;
+ goto done;
+ }
+
+ /* Only oauth2 type is supported at the moment. */
+ if (strcmp(idpcfg->type, "oauth2") != 0) {
+ ret = EINVAL;
+ goto done;
+ }
+
+ idpcfg->type = strdup(idpcfg->type);
+ if (idpcfg->type == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ /* Are indicators set? */
+ if (jindicators != NULL) {
+ idpcfg->indicators = sss_json_array_to_strings(jindicators);
+ if (idpcfg->indicators == NULL) {
+ ret = EINVAL;
+ goto done;
+ }
+ }
+
+ *_idpcfg = idpcfg;
+
+ ret = 0;
+
+done:
+ if (ret != 0) {
+ sss_idp_config_free(idpcfg);
+ }
+
+ if (jroot != NULL) {
+ json_decref(jroot);
+ }
+
+ return ret;
+}
+
+void
+sss_idp_oauth2_free(struct sss_idp_oauth2 *data)
+{
+ if (data == NULL) {
+ return;
+ }
+
+ free(data->verification_uri);
+ free(data->verification_uri_complete);
+ free(data->user_code);
+ free(data);
+}
+
+static struct sss_idp_oauth2 *
+sss_idp_oauth2_init(const char *verification_uri,
+ const char *verification_uri_complete,
+ const char *user_code)
+{
+ struct sss_idp_oauth2 *data;
+
+ /* These are required fields. */
+ if (is_empty(verification_uri) || is_empty(user_code)) {
+ return NULL;
+ }
+
+ data = malloc(sizeof(struct sss_idp_oauth2));
+ if (data == NULL) {
+ return NULL;
+ }
+ memset(data, 0, sizeof(struct sss_idp_oauth2));
+
+ data->verification_uri = strdup(verification_uri);
+ data->user_code = strdup(user_code);
+ if (data->verification_uri == NULL || data->user_code == NULL) {
+ sss_idp_oauth2_free(data);
+ return NULL;
+ }
+
+ if (!is_empty(verification_uri_complete)) {
+ data->verification_uri_complete = strdup(verification_uri_complete);
+ if (data->verification_uri_complete == NULL) {
+ sss_idp_oauth2_free(data);
+ return NULL;
+ }
+ }
+
+ return data;
+}
+
+static struct sss_idp_oauth2 *
+sss_idp_oauth2_from_json(const char *json_str)
+{
+ struct sss_idp_oauth2 jdata = {0};
+ struct sss_idp_oauth2 *data;
+ json_error_t jret;
+ json_t *jroot;
+ int ret;
+
+ jroot = json_loads(json_str, 0, &jret);
+ if (jroot == NULL) {
+ return NULL;
+ }
+
+ ret = json_unpack(jroot, "{s:s, s?:s, s:s}",
+ "verification_uri", &jdata.verification_uri,
+ "verification_uri_complete", &jdata.verification_uri_complete,
+ "user_code", &jdata.user_code);
+ if (ret != 0) {
+ json_decref(jroot);
+ return NULL;
+ }
+
+ data = sss_idp_oauth2_init(jdata.verification_uri,
+ jdata.verification_uri_complete,
+ jdata.user_code);
+
+ json_decref(jroot);
+ return data;
+}
+
+static char *
+sss_idp_oauth2_to_json(const struct sss_idp_oauth2 *data)
+{
+ json_t *jroot;
+ char *str;
+
+ if (data == NULL) {
+ return NULL;
+ }
+
+ /* These are required fields. */
+ if (data->verification_uri == NULL || data->user_code == NULL) {
+ return NULL;
+ }
+
+ jroot = json_pack("{s:s?, s:s*, s:s?}",
+ "verification_uri", data->verification_uri,
+ "verification_uri_complete", data->verification_uri_complete,
+ "user_code", data->user_code);
+ if (jroot == NULL) {
+ return NULL;
+ }
+
+ str = json_dumps(jroot, JSON_COMPACT);
+ json_decref(jroot);
+
+ return str;
+}
+
+static struct sss_idp_oauth2 *
+sss_idp_oauth2_decode(const char *str)
+{
+ return sss_radius_message_decode(SSSD_IDP_OAUTH2_PREFIX,
+ (sss_radius_message_decode_fn)sss_idp_oauth2_from_json, str);
+}
+
+static char *
+sss_idp_oauth2_encode(struct sss_idp_oauth2 *data)
+{
+ return sss_radius_message_encode(SSSD_IDP_OAUTH2_PREFIX,
+ (sss_radius_message_encode_fn)sss_idp_oauth2_to_json, data);
+}
+
+krb5_pa_data *
+sss_idp_oauth2_encode_padata(struct sss_idp_oauth2 *data)
+{
+ return sss_radius_encode_padata(SSSD_IDP_OAUTH2_PADATA,
+ (sss_radius_message_encode_fn)sss_idp_oauth2_encode, data);
+}
+
+struct sss_idp_oauth2 *
+sss_idp_oauth2_decode_padata(krb5_pa_data *padata)
+{
+ return sss_radius_decode_padata(
+ (sss_radius_message_decode_fn)sss_idp_oauth2_decode, padata);
+}
+
+char *
+sss_idp_oauth2_encode_challenge(struct sss_idp_oauth2 *data)
+{
+ return sss_idp_oauth2_encode(data);
+}
+
+struct sss_idp_oauth2 *
+sss_idp_oauth2_decode_challenge(const char *str)
+{
+ return sss_idp_oauth2_decode(str);
+}
diff --git a/src/krb5_plugin/idp/sssd_enable_idp b/src/krb5_plugin/idp/sssd_enable_idp
new file mode 100644
index 0000000..8f98174
--- /dev/null
+++ b/src/krb5_plugin/idp/sssd_enable_idp
@@ -0,0 +1,14 @@
+# Enable SSSD OAuth2 Kerberos preauthentication plugins.
+#
+# This will allow you to obtain Kerberos TGT through OAuth2 authentication.
+#
+# To disable the OAuth2 plugin, comment out the following lines.
+
+[plugins]
+ clpreauth = {
+ module = idp:/usr/lib64/sssd/modules/sssd_krb5_idp_plugin.so
+ }
+
+ kdcpreauth = {
+ module = idp:/usr/lib64/sssd/modules/sssd_krb5_idp_plugin.so
+ }
diff --git a/src/krb5_plugin/passkey/passkey.h b/src/krb5_plugin/passkey/passkey.h
new file mode 100644
index 0000000..0f36d5d
--- /dev/null
+++ b/src/krb5_plugin/passkey/passkey.h
@@ -0,0 +1,110 @@
+/*
+ Authors:
+ Pavel Březina <pbrezina@redhat.com>
+
+ Copyright (C) 2023 Red Hat
+
+ 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 _PASSKEY_H_
+#define _PASSKEY_H_
+
+#include <stdlib.h>
+#include <krb5/preauth_plugin.h>
+
+#ifndef discard_const
+#define discard_const(ptr) ((void *)((uintptr_t)(ptr)))
+#endif
+
+#define SSSD_PASSKEY_PLUGIN "passkey"
+#define SSSD_PASSKEY_CONFIG "passkey"
+#define SSSD_PASSKEY_PADATA 153 // PA-REDHAT-PASSKEY
+#define SSSD_PASSKEY_QUESTION "passkey"
+#define SSSD_PASSKEY_PREFIX "passkey "
+#define SSSD_PASSKEY_REPLY_STATE "ipa_otpd state"
+#define SSSD_PASSKEY_PROMPT "Insert your passkey device, then press ENTER"
+#define SSSD_PASSKEY_PIN_PROMPT "Enter PIN"
+#define SSSD_PASSKEY_CHILD SSSD_LIBEXEC_PATH"/passkey_child"
+
+struct sss_passkey_config {
+ char **indicators;
+};
+
+void
+sss_passkey_config_free(struct sss_passkey_config *passkey);
+
+krb5_error_code
+sss_passkey_config_init(const char *config,
+ struct sss_passkey_config **_passkey);
+
+enum sss_passkey_phase {
+ SSS_PASSKEY_PHASE_INIT,
+ SSS_PASSKEY_PHASE_CHALLENGE,
+ SSS_PASSKEY_PHASE_REPLY
+};
+
+struct sss_passkey_challenge {
+ char *domain;
+ char **credential_id_list;
+ int user_verification;
+ char *cryptographic_challenge;
+};
+
+struct sss_passkey_reply {
+ char *credential_id;
+ char *cryptographic_challenge;
+ char *authenticator_data;
+ char *assertion_signature;
+ char *user_id;
+};
+
+struct sss_passkey_message {
+ enum sss_passkey_phase phase;
+ char *state;
+ union {
+ struct sss_passkey_challenge *challenge;
+ struct sss_passkey_reply *reply;
+ void *ptr;
+ } data;
+};
+
+void
+sss_passkey_message_free(struct sss_passkey_message *message);
+
+struct sss_passkey_message *
+sss_passkey_message_from_reply_json(enum sss_passkey_phase phase,
+ const char *state,
+ const char *json_str);
+
+char *
+sss_passkey_message_encode(const struct sss_passkey_message *data);
+
+struct sss_passkey_message *
+sss_passkey_message_decode(const char *str);
+
+krb5_pa_data *
+sss_passkey_message_encode_padata(const struct sss_passkey_message *data);
+
+struct sss_passkey_message *
+sss_passkey_message_decode_padata(krb5_pa_data *padata);
+
+krb5_pa_data **
+sss_passkey_message_encode_padata_array(const struct sss_passkey_message *data);
+
+krb5_error_code
+sss_passkey_concat_credentials(char **creds,
+ char **_creds_str);
+
+#endif /* _PASSKEY_H_ */
diff --git a/src/krb5_plugin/passkey/passkey_clpreauth.c b/src/krb5_plugin/passkey/passkey_clpreauth.c
new file mode 100644
index 0000000..d2dfe6f
--- /dev/null
+++ b/src/krb5_plugin/passkey/passkey_clpreauth.c
@@ -0,0 +1,395 @@
+/*
+ Authors:
+ Pavel Březina <pbrezina@redhat.com>
+
+ Copyright (C) 2023 Red Hat
+
+ 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 "config.h"
+
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <krb5/clpreauth_plugin.h>
+
+#include "krb5_plugin/common/radius_clpreauth.h"
+#include "passkey.h"
+
+#include "util/child_common.h"
+
+static krb5_error_code
+sss_passkeycl_prompt(krb5_context context,
+ krb5_prompter_fct prompter,
+ void *prompter_data,
+ struct sss_passkey_message *message,
+ const char *prompt_txt,
+ char *prompt_answer,
+ int answer_len,
+ krb5_data *_reply)
+{
+ krb5_error_code ret;
+ krb5_prompt prompt;
+ char *prompt_str;
+ int aret;
+
+ _reply->magic = 0;
+ _reply->length = answer_len;
+ _reply->data = prompt_answer;
+
+ aret = asprintf(&prompt_str, "%s", prompt_txt);
+ if (aret < 0) {
+ return ENOMEM;
+ }
+
+ prompt.reply = _reply;
+ prompt.prompt = prompt_str;
+ prompt.hidden = 1;
+
+ ret = (*prompter)(context, prompter_data, NULL, NULL, 1, &prompt);
+ free(prompt_str);
+
+ return ret;
+}
+
+static krb5_error_code
+sss_passkeycl_prep_questions(krb5_context context,
+ krb5_clpreauth_moddata moddata,
+ krb5_clpreauth_modreq modreq,
+ krb5_get_init_creds_opt *opt,
+ krb5_clpreauth_callbacks cb,
+ krb5_clpreauth_rock rock,
+ krb5_kdc_req *request,
+ krb5_data *encoded_request_body,
+ krb5_data *encoded_previous_request,
+ krb5_pa_data *pa_data)
+{
+ struct sss_passkey_message *message;
+ char *question = NULL;
+ char **realms = NULL;
+ krb5_error_code ret;
+ size_t r = 0;
+
+ message = sss_passkey_message_decode_padata(pa_data);
+ if (message == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ if (message->data.challenge->domain == NULL) {
+ ret = KRB5KDC_ERR_PREAUTH_FAILED;
+ goto done;
+ }
+
+ /* Find a realm that matches the domain from the challenge */
+ ret = krb5_get_host_realm(context,
+ message->data.challenge->domain,
+ &realms);
+ if (ret || ((realms == NULL) || (realms[0] == NULL))) {
+ ret = KRB5KDC_ERR_PREAUTH_FAILED;
+ goto done;
+ }
+
+ /* Do explicit check for the challenge domain in case
+ * we've got back a referral (empty) realm */
+ if (strlen(realms[0]) == strlen(KRB5_REFERRAL_REALM)) {
+ ret = strncasecmp(message->data.challenge->domain,
+ request->server->realm.data,
+ request->server->realm.length);
+ if (ret != 0) {
+ ret = KRB5KDC_ERR_PREAUTH_FAILED;
+ goto done;
+ }
+
+ } else {
+ for(r = 0; realms[r] != NULL; r++) {
+ ret = strncasecmp(realms[r],
+ request->server->realm.data,
+ request->server->realm.length);
+ if (ret == 0) {
+ break;
+ }
+ }
+
+ /* doesn't know the domain, reject the challenge */
+ if (realms[r] == NULL) {
+ ret = KRB5KDC_ERR_PREAUTH_FAILED;
+ goto done;
+ }
+ }
+
+ question = sss_passkey_message_encode(message);
+ if (question == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ ret = cb->ask_responder_question(context, rock, SSSD_PASSKEY_QUESTION,
+ question);
+
+done:
+ if (realms) {
+ krb5_free_host_realm(context, realms);
+ }
+ sss_passkey_message_free(message);
+ free(question);
+
+ return ret;
+}
+
+static krb5_error_code
+sss_passkeycl_exec_child(struct sss_passkey_challenge *data,
+ char *pin,
+ uint8_t **_reply)
+{
+ int pipe_to_child[2];
+ int pipe_to_parent[2];
+ pid_t cpid;
+ char *args[10] = {NULL};
+ int arg_c = 0;
+ int size;
+ uint8_t *buf;
+ int ret = 0;
+ char *result_creds;
+
+ buf = calloc(1, CHILD_MSG_CHUNK);
+ if (buf == NULL) {
+ ret = ENOMEM;
+ return ret;
+ }
+
+ ret = sss_passkey_concat_credentials(data->credential_id_list,
+ &result_creds);
+ if (ret != 0) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ args[arg_c++] = discard_const(SSSD_PASSKEY_CHILD);
+ args[arg_c++] = discard_const("--get-assert");
+ args[arg_c++] = discard_const("--domain");
+ args[arg_c++] = data->domain;
+ args[arg_c++] = discard_const("--key-handle");
+ args[arg_c++] = discard_const(result_creds);
+ args[arg_c++] = discard_const("--cryptographic-challenge");
+ args[arg_c++] = data->cryptographic_challenge;
+ args[arg_c++] = NULL;
+
+ ret = pipe(pipe_to_child);
+ if (ret == -1) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ ret = pipe(pipe_to_parent);
+ if (ret == -1) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ cpid = fork();
+ /* Child */
+ if (cpid == 0) {
+ close(pipe_to_child[1]);
+ dup2(pipe_to_child[0], STDIN_FILENO);
+
+ close(pipe_to_parent[0]);
+ dup2(pipe_to_parent[1], STDOUT_FILENO);
+
+ execv(SSSD_PASSKEY_CHILD, args);
+ exit(EXIT_FAILURE);
+ /* Parent - write PIN to child and read output
+ * back from child */
+ } else {
+ close(pipe_to_child[0]);
+ close(pipe_to_parent[1]);
+
+ write(pipe_to_child[1], pin, strlen(pin));
+ close(pipe_to_child[1]);
+
+ size = read(pipe_to_parent[0], buf, CHILD_MSG_CHUNK);
+ if (size == -1) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ close(pipe_to_parent[0]);
+ wait(NULL);
+ }
+
+ *_reply = buf;
+
+done:
+ if (ret != 0) {
+ free(buf);
+ }
+
+ free(result_creds);
+ return ret;
+}
+
+static krb5_error_code
+sss_passkeycl_process(krb5_context context,
+ krb5_clpreauth_moddata moddata,
+ krb5_clpreauth_modreq modreq,
+ krb5_get_init_creds_opt *opt,
+ krb5_clpreauth_callbacks cb,
+ krb5_clpreauth_rock rock,
+ krb5_kdc_req *request,
+ krb5_data *encoded_request_body,
+ krb5_data *encoded_previous_request,
+ krb5_pa_data *pa_data,
+ krb5_prompter_fct prompter,
+ void *prompter_data,
+ krb5_pa_data ***_pa_data_out)
+{
+ krb5_keyblock *as_key;
+ krb5_pa_data **padata;
+ krb5_error_code ret;
+ krb5_data user_reply;
+ struct sss_passkey_message *input_message = NULL;
+ struct sss_passkey_message *reply_message = NULL;
+ struct sss_passkey_message *reply_msg = NULL;
+ enum sss_passkey_phase phase;
+ const char *state;
+ char prompt_answer[255] = {0};
+ int answer_len;
+ char *prompt_reply = NULL;
+ uint8_t *reply = NULL;
+ const char *answer;
+
+ input_message = sss_passkey_message_decode_padata(pa_data);
+ if (input_message == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ /* Get FAST armor key. */
+ as_key = cb->fast_armor(context, rock);
+ if (as_key == NULL) {
+ ret = ENOENT;
+ goto done;
+ }
+
+ answer = cb->get_responder_answer(context, rock, SSSD_PASSKEY_QUESTION);
+ /* Call prompter if we have no answer to present a prompt. */
+ if (answer == NULL) {
+ /* Interactive prompt */
+ answer_len = sizeof(prompt_answer) / sizeof(char);
+
+ ret = sss_passkeycl_prompt(context, prompter, prompter_data,
+ input_message, SSSD_PASSKEY_PROMPT,
+ prompt_answer, answer_len,
+ &user_reply);
+ if (ret != 0) {
+ goto done;
+ }
+
+ /* Prompt for PIN */
+ if (input_message->data.challenge->user_verification == 1) {
+ ret = sss_passkeycl_prompt(context, prompter, prompter_data,
+ input_message, SSSD_PASSKEY_PIN_PROMPT,
+ prompt_answer, answer_len,
+ &user_reply);
+ if (ret != 0) {
+ goto done;
+ }
+ }
+
+ ret = sss_passkeycl_exec_child(input_message->data.challenge, prompt_answer, &reply);
+ if (ret != 0) {
+ goto done;
+ }
+
+ phase = SSS_PASSKEY_PHASE_REPLY;
+ state = SSSD_PASSKEY_REPLY_STATE;
+ reply_msg = sss_passkey_message_from_reply_json(phase, state, (char *)reply);
+ if (reply_msg == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ prompt_reply = sss_passkey_message_encode(reply_msg);
+ if (prompt_reply == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+ }
+
+ /* Use FAST armor key as response key. */
+ ret = cb->set_as_key(context, rock, as_key);
+ if (ret != 0) {
+ goto done;
+ }
+
+ /* Encode the answer into the pa_data output. */
+ if (prompt_reply != NULL) {
+ reply_message = sss_passkey_message_decode(prompt_reply);
+ } else {
+ reply_message = sss_passkey_message_decode(answer);
+ }
+ if (reply_message == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ padata = sss_passkey_message_encode_padata_array(reply_message);
+ if (padata == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ cb->disable_fallback(context, rock);
+ *_pa_data_out = padata;
+
+ ret = 0;
+
+done:
+ sss_passkey_message_free(reply_message);
+ sss_passkey_message_free(reply_msg);
+ sss_passkey_message_free(input_message);
+ free(reply);
+ free(prompt_reply);
+
+ return ret;
+}
+
+krb5_error_code
+clpreauth_passkey_initvt(krb5_context context,
+ int maj_ver,
+ int min_ver,
+ krb5_plugin_vtable vtable)
+{
+ static krb5_preauthtype pa_type_list[] = { SSSD_PASSKEY_PADATA, 0 };
+ krb5_clpreauth_vtable vt;
+
+ if (maj_ver != 1) {
+ return KRB5_PLUGIN_VER_NOTSUPP;
+ }
+
+ vt = (krb5_clpreauth_vtable)vtable;
+ vt->name = discard_const(SSSD_PASSKEY_PLUGIN);
+ vt->pa_type_list = pa_type_list;
+ vt->request_init = sss_radiuscl_init;
+ vt->prep_questions = sss_passkeycl_prep_questions;
+ vt->process = sss_passkeycl_process;
+ vt->request_fini = sss_radiuscl_fini;
+ vt->gic_opts = NULL;
+
+ return 0;
+}
diff --git a/src/krb5_plugin/passkey/passkey_kdcpreauth.c b/src/krb5_plugin/passkey/passkey_kdcpreauth.c
new file mode 100644
index 0000000..903cb42
--- /dev/null
+++ b/src/krb5_plugin/passkey/passkey_kdcpreauth.c
@@ -0,0 +1,438 @@
+/*
+ Authors:
+ Pavel Březina <pbrezina@redhat.com>
+
+ Copyright (C) 2023 Red Hat
+
+ 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 "config.h"
+
+#include <errno.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <limits.h>
+#include <krad.h>
+#include <krb5/kdcpreauth_plugin.h>
+
+#include "shared/safealign.h"
+#include "krb5_plugin/common/radius_kdcpreauth.h"
+#include "krb5_plugin/common/utils.h"
+#include "krb5_plugin/passkey/passkey.h"
+#include "util/util.h"
+
+struct sss_passkeykdc_config {
+ struct sss_radiuskdc_config *radius;
+ struct sss_passkey_config *passkey;
+};
+
+static void
+sss_passkeykdc_config_free(struct sss_passkeykdc_config *config)
+{
+ if (config == NULL) {
+ return;
+ }
+
+ sss_radiuskdc_config_free(config->radius);
+ sss_passkey_config_free(config->passkey);
+ free(config);
+}
+
+static krb5_error_code
+sss_passkeykdc_config_init(struct sss_radiuskdc_state *state,
+ krb5_context kctx,
+ krb5_const_principal princ,
+ const char *configstr,
+ struct sss_passkeykdc_config **_config)
+{
+ struct sss_passkeykdc_config *config;
+ krb5_error_code ret;
+
+ if (state == NULL) {
+ return EINVAL;
+ }
+
+ config = malloc(sizeof(struct sss_passkeykdc_config));
+ if (config == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+ memset(config, 0, sizeof(struct sss_passkeykdc_config));
+
+ ret = sss_radiuskdc_config_init(state, kctx, princ, configstr, &config->radius);
+ if (ret != 0) {
+ goto done;
+ }
+
+ ret = sss_passkey_config_init(configstr, &config->passkey);
+ if (ret != 0) {
+ goto done;
+ }
+
+
+ *_config = config;
+ ret = 0;
+
+done:
+ if (ret != 0) {
+ sss_passkeykdc_config_free(config);
+ }
+
+ return ret;
+}
+
+static void
+sss_passkeykdc_challenge_done(krb5_error_code rret,
+ const krad_packet *rreq,
+ const krad_packet *rres,
+ void *data)
+{
+ struct sss_passkey_message *message = NULL;
+ struct sss_radiuskdc_challenge *state;
+ krb5_pa_data *padata = NULL;
+ krb5_data cookie = {0};
+ char *reply = NULL;
+ krb5_error_code ret;
+
+ state = (struct sss_radiuskdc_challenge *)data;
+
+ if (rret != 0) {
+ ret = rret;
+ goto done;
+ }
+
+ if (krad_packet_get_code(rres) != krad_code_name2num("Access-Challenge")) {
+ ret = ENOENT;
+ goto done;
+ }
+
+ reply = sss_radiuskdc_get_attr_as_string(rres, "Proxy-State");
+ if (reply == NULL) {
+ ret = EINVAL;
+ goto done;
+ }
+
+ message = sss_passkey_message_decode(reply);
+ if (message == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ if (message->phase != SSS_PASSKEY_PHASE_CHALLENGE) {
+ ret = EINVAL;
+ goto done;
+ }
+
+ /* Remember the whole passkey challenge message in cookie so we can later
+ * verify that the client response is really associated with this request.
+ */
+ cookie.data = reply;
+ cookie.length = strlen(reply) + 1;
+ ret = sss_radiuskdc_set_cookie(state->kctx, state->cb, state->rock,
+ SSSD_PASSKEY_PADATA, &cookie);
+ if (ret != 0) {
+ goto done;
+ }
+
+ padata = sss_passkey_message_encode_padata(message);
+ if (padata == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ ret = 0;
+
+done:
+ state->respond(state->arg, ret, padata);
+ sss_radiuskdc_challenge_free(state);
+ sss_passkey_message_free(message);
+ free(reply);
+
+ /* padata should not be freed */
+
+ return;
+}
+
+/* Send a password-less Access-Request and expect Access-Challenge response. */
+static krb5_error_code
+sss_passkeykdc_challenge_send(krb5_context kctx,
+ krb5_kdcpreauth_callbacks cb,
+ krb5_kdcpreauth_rock rock,
+ krb5_kdcpreauth_edata_respond_fn respond,
+ void *arg,
+ struct sss_radiuskdc_config *config)
+{
+ struct sss_passkey_message message;
+ struct sss_radiuskdc_challenge *state;
+ char *encoded_message = NULL;
+ krb5_error_code ret;
+
+ state = sss_radiuskdc_challenge_init(kctx, cb, rock, respond, arg, config);
+ if (state == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ message.phase = SSS_PASSKEY_PHASE_INIT;
+ message.state = NULL;
+ message.data.challenge = NULL;
+
+ encoded_message = sss_passkey_message_encode(&message);
+ if (encoded_message == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ ret = sss_radiuskdc_set_attr_as_string(state->client->attrs,
+ "Proxy-State",
+ encoded_message);
+ if (ret != 0) {
+ goto done;
+ }
+
+ ret = krad_client_send(state->client->client,
+ krad_code_name2num("Access-Request"),
+ state->client->attrs, config->server,
+ config->secret, config->timeout, config->retries,
+ sss_passkeykdc_challenge_done, state);
+
+done:
+ free(encoded_message);
+
+ if (ret != 0) {
+ sss_radiuskdc_challenge_free(state);
+ }
+
+ return ret;
+}
+
+/* Send Access-Request with password and state set to indicate that the user has
+ * finished authentication against passkey provider. We expect Access-Accept. */
+static krb5_error_code
+sss_passkeykdc_verify_send(krb5_context kctx,
+ krb5_kdcpreauth_rock rock,
+ krb5_kdcpreauth_callbacks cb,
+ krb5_enc_tkt_part *enc_tkt_reply,
+ krb5_kdcpreauth_verify_respond_fn respond,
+ void *arg,
+ const struct sss_passkey_message *message,
+ char **indicators,
+ struct sss_radiuskdc_config *config)
+{
+ struct sss_radiuskdc_verify *state;
+ char *encoded_message = NULL;
+ krb5_error_code ret;
+
+ state = sss_radiuskdc_verify_init(kctx, rock, cb, enc_tkt_reply, respond,
+ arg, indicators, config);
+ if (state == NULL) {
+ return ENOMEM;
+ }
+
+ encoded_message = sss_passkey_message_encode(message);
+ if (encoded_message == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ ret = sss_radiuskdc_set_attr_as_string(state->client->attrs,
+ "Proxy-State",
+ encoded_message);
+ if (ret != 0) {
+ goto done;
+ }
+
+ ret = krad_client_send(state->client->client,
+ krad_code_name2num("Access-Request"),
+ state->client->attrs, config->server,
+ config->secret, config->timeout, config->retries,
+ sss_radiuskdc_verify_done, state);
+
+done:
+ free(encoded_message);
+
+ if (ret != 0) {
+ sss_radiuskdc_verify_free(state);
+ }
+
+ return ret;
+}
+
+static krb5_error_code
+sss_passkeykdc_init(krb5_context kctx,
+ krb5_kdcpreauth_moddata *_moddata,
+ const char **_realmnames)
+{
+ return sss_radiuskdc_init(SSSD_PASSKEY_PLUGIN, kctx, _moddata, _realmnames);
+}
+
+static void
+sss_passkeykdc_edata(krb5_context kctx,
+ krb5_kdc_req *request,
+ krb5_kdcpreauth_callbacks cb,
+ krb5_kdcpreauth_rock rock,
+ krb5_kdcpreauth_moddata moddata,
+ krb5_preauthtype pa_type,
+ krb5_kdcpreauth_edata_respond_fn respond,
+ void *arg)
+{
+ struct sss_passkeykdc_config *config = NULL;
+ struct sss_radiuskdc_state *state;
+ krb5_keyblock *armor_key;
+ char *configstr = NULL;
+ krb5_error_code ret;
+
+ state = (struct sss_radiuskdc_state *)moddata;
+
+ ret = sss_radiuskdc_enabled(SSSD_PASSKEY_CONFIG, kctx, cb, rock, &configstr);
+ if (ret != 0) {
+ goto done;
+ }
+
+ armor_key = cb->fast_armor(kctx, rock);
+ if (armor_key == NULL) {
+ ret = ENOENT;
+ goto done;
+ }
+
+ ret = sss_passkeykdc_config_init(state, kctx, cb->client_name(kctx, rock),
+ configstr, &config);
+ if (ret != 0) {
+ goto done;
+ }
+
+ ret = sss_passkeykdc_challenge_send(kctx, cb, rock, respond, arg,
+ config->radius);
+
+done:
+ if (ret != 0) {
+ respond(arg, ret, NULL);
+ }
+
+ cb->free_string(kctx, rock, configstr);
+ sss_passkeykdc_config_free(config);
+}
+
+static void
+sss_passkeykdc_verify(krb5_context kctx,
+ krb5_data *req_pkt,
+ krb5_kdc_req *request,
+ krb5_enc_tkt_part *enc_tkt_reply,
+ krb5_pa_data *pa,
+ krb5_kdcpreauth_callbacks cb,
+ krb5_kdcpreauth_rock rock,
+ krb5_kdcpreauth_moddata moddata,
+ krb5_kdcpreauth_verify_respond_fn respond,
+ void *arg)
+{
+ struct sss_radiuskdc_state *state;
+ struct sss_passkeykdc_config *config = NULL;
+ struct sss_passkey_message *message = NULL;
+ struct sss_passkey_message *challenge = NULL;
+ char *configstr = NULL;
+ krb5_error_code ret;
+ krb5_data cookie;
+
+ state = (struct sss_radiuskdc_state *)moddata;
+
+ ret = sss_radiuskdc_enabled(SSSD_PASSKEY_CONFIG, kctx, cb, rock, &configstr);
+ if (ret != 0) {
+ goto done;
+ }
+
+ ret = sss_passkeykdc_config_init(state, kctx, cb->client_name(kctx, rock),
+ configstr, &config);
+ if (ret != 0) {
+ goto done;
+ }
+
+ ret = sss_radiuskdc_get_cookie(kctx, cb, rock, SSSD_PASSKEY_PADATA,
+ &cookie);
+ if (ret != 0) {
+ goto done;
+ }
+
+ challenge = sss_passkey_message_decode(cookie.data);
+ if (challenge == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ if (pa->pa_type != SSSD_PASSKEY_PADATA || pa->length == 0) {
+ ret = KRB5_PREAUTH_BAD_TYPE;
+ goto done;
+ }
+
+ message = sss_passkey_message_decode_padata(pa);
+ if (message == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ if (message->phase != SSS_PASSKEY_PHASE_REPLY
+ || strcmp(message->state, challenge->state) != 0
+ || strcmp(message->data.reply->cryptographic_challenge,
+ challenge->data.challenge->cryptographic_challenge) != 0) {
+ ret = EINVAL;
+ goto done;
+ }
+
+ ret = sss_passkeykdc_verify_send(kctx, rock, cb, enc_tkt_reply, respond,
+ arg, message, config->passkey->indicators, config->radius);
+ if (ret != 0) {
+ goto done;
+ }
+
+ ret = 0;
+
+done:
+ if (ret != 0) {
+ respond(arg, ret, NULL, NULL, NULL);
+ }
+
+ cb->free_string(kctx, rock, configstr);
+ sss_passkeykdc_config_free(config);
+ sss_passkey_message_free(message);
+ sss_passkey_message_free(challenge);
+}
+
+krb5_error_code
+kdcpreauth_passkey_initvt(krb5_context kctx,
+ int maj_ver,
+ int min_ver,
+ krb5_plugin_vtable vtable)
+{
+ static krb5_preauthtype pa_type_list[] = { SSSD_PASSKEY_PADATA, 0 };
+ krb5_kdcpreauth_vtable vt;
+
+ if (maj_ver != 1) {
+ return KRB5_PLUGIN_VER_NOTSUPP;
+ }
+
+ vt = (krb5_kdcpreauth_vtable)vtable;
+ vt->name = discard_const(SSSD_PASSKEY_PLUGIN);
+ vt->pa_type_list = pa_type_list;
+ vt->init = sss_passkeykdc_init;
+ vt->fini = sss_radiuskdc_fini;
+ vt->flags = sss_radiuskdc_flags;
+ vt->edata = sss_passkeykdc_edata;
+ vt->verify = sss_passkeykdc_verify;
+ vt->return_padata = sss_radiuskdc_return_padata;
+
+ com_err(SSSD_PASSKEY_PLUGIN, 0, "SSSD passkey plugin loaded");
+
+ return 0;
+}
diff --git a/src/krb5_plugin/passkey/passkey_utils.c b/src/krb5_plugin/passkey/passkey_utils.c
new file mode 100644
index 0000000..e73da97
--- /dev/null
+++ b/src/krb5_plugin/passkey/passkey_utils.c
@@ -0,0 +1,648 @@
+/*
+ Authors:
+ Pavel Březina <pbrezina@redhat.com>
+
+ Copyright (C) 2023 Red Hat
+
+ 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 "config.h"
+
+#include <errno.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <jansson.h>
+#include <arpa/inet.h>
+#include <krb5/preauth_plugin.h>
+
+#include "krb5_plugin/common/utils.h"
+#include "krb5_plugin/passkey/passkey.h"
+
+void
+sss_passkey_config_free(struct sss_passkey_config *passkey)
+{
+ if (passkey == NULL) {
+ return;
+ }
+
+ sss_string_array_free(passkey->indicators);
+ free(passkey);
+}
+
+/**
+ * {
+ * "indicators": ["..."] (optional)
+ * }
+ */
+krb5_error_code
+sss_passkey_config_init(const char *config,
+ struct sss_passkey_config **_passkey)
+{
+ struct sss_passkey_config *passkey;
+ json_t *jindicators = NULL;
+ json_error_t jret;
+ json_t *jroot;
+ krb5_error_code ret;
+
+ passkey = malloc(sizeof(struct sss_passkey_config));
+ if (passkey == NULL) {
+ return ENOMEM;
+ }
+ memset(passkey, 0, sizeof(struct sss_passkey_config));
+
+ jroot = json_loads(config, 0, &jret);
+ if (jroot == NULL) {
+ ret = EINVAL;
+ goto done;
+ }
+
+ ret = json_unpack(jroot, "[{s?:o}]", "indicators", &jindicators);
+ if (ret != 0) {
+ ret = EINVAL;
+ goto done;
+ }
+
+ /* Are indicators set? */
+ if (jindicators != NULL) {
+ passkey->indicators = sss_json_array_to_strings(jindicators);
+ if (passkey->indicators == NULL) {
+ ret = EINVAL;
+ goto done;
+ }
+ }
+
+ *_passkey = passkey;
+
+ ret = 0;
+
+done:
+ if (ret != 0) {
+ sss_passkey_config_free(passkey);
+ }
+
+ if (jroot != NULL) {
+ json_decref(jroot);
+ }
+
+ return ret;
+}
+
+void
+sss_passkey_challenge_free(struct sss_passkey_challenge *data)
+{
+ if (data == NULL) {
+ return;
+ }
+
+ free(data->domain);
+ free(data->cryptographic_challenge);
+ sss_string_array_free(data->credential_id_list);
+
+ free(data);
+}
+
+static struct sss_passkey_challenge *
+sss_passkey_challenge_init(char *domain,
+ char **credential_id_list,
+ int user_verification,
+ char *cryptographic_challenge)
+{
+ struct sss_passkey_challenge *data;
+ krb5_error_code ret;
+
+ /* These are required fields. */
+ if (is_empty(domain)
+ || is_empty(cryptographic_challenge)
+ || credential_id_list == NULL || is_empty(credential_id_list[0])) {
+ return NULL;
+ }
+
+ data = malloc(sizeof(struct sss_passkey_challenge));
+ if (data == NULL) {
+ return NULL;
+ }
+ memset(data, 0, sizeof(struct sss_passkey_challenge));
+
+ data->user_verification = user_verification;
+ data->domain = strdup(domain);
+ data->cryptographic_challenge = strdup(cryptographic_challenge);
+ if (data->domain == NULL || data->cryptographic_challenge == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ data->credential_id_list = sss_string_array_copy(credential_id_list);
+ if (data->credential_id_list == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ ret = 0;
+
+done:
+ if (ret != 0) {
+ sss_passkey_challenge_free(data);
+ return NULL;
+ }
+
+ return data;
+}
+
+static struct sss_passkey_challenge *
+sss_passkey_challenge_from_json_object(json_t *jobject)
+{
+ struct sss_passkey_challenge jdata = {0};
+ struct sss_passkey_challenge *data = NULL;
+ json_t *jcredential_id_list = NULL;
+ char **credential_id_list = NULL;
+ int ret;
+
+ if (jobject == NULL) {
+ return NULL;
+ }
+
+ ret = json_unpack(jobject, "{s:s, s:o, s:i, s:s}",
+ "domain", &jdata.domain,
+ "credential_id_list", &jcredential_id_list,
+ "user_verification", &jdata.user_verification,
+ "cryptographic_challenge", &jdata.cryptographic_challenge);
+ if (ret != 0) {
+ return NULL;
+ }
+
+ if (jcredential_id_list != NULL) {
+ credential_id_list = sss_json_array_to_strings(jcredential_id_list);
+ if (credential_id_list == NULL) {
+ return NULL;
+ }
+ }
+
+ data = sss_passkey_challenge_init(jdata.domain, credential_id_list,
+ jdata.user_verification,
+ jdata.cryptographic_challenge);
+
+ sss_string_array_free(credential_id_list);
+ return data;
+}
+
+static json_t *
+sss_passkey_challenge_to_json_object(const struct sss_passkey_challenge *data)
+{
+ json_t *jroot;
+ json_t *jcredential_id_list;
+
+ if (data == NULL) {
+ return NULL;
+ }
+
+ /* These are required fields. */
+ if (data->domain == NULL || data->credential_id_list == NULL
+ || data->cryptographic_challenge == NULL) {
+ return NULL;
+ }
+
+ jcredential_id_list = sss_strings_to_json_array(data->credential_id_list);
+ if (jcredential_id_list == NULL) {
+ return NULL;
+ }
+
+ jroot = json_pack("{s:s, s:o, s:i, s:s}", "domain", data->domain,
+ "credential_id_list", jcredential_id_list,
+ "user_verification", data->user_verification,
+ "cryptographic_challenge",
+ data->cryptographic_challenge);
+ if (jroot == NULL) {
+ json_decref(jcredential_id_list);
+ return NULL;
+ }
+
+ return jroot;
+}
+
+void
+sss_passkey_reply_free(struct sss_passkey_reply *data)
+{
+ if (data == NULL) {
+ return;
+ }
+
+ free(data->credential_id);
+ free(data->cryptographic_challenge);
+ free(data->authenticator_data);
+ free(data->assertion_signature);
+ free(data->user_id);
+ free(data);
+}
+
+static struct sss_passkey_reply *
+sss_passkey_reply_init(char *credential_id,
+ char *cryptographic_challenge,
+ char *authenticator_data,
+ char *assertion_signature,
+ char *user_id)
+{
+ struct sss_passkey_reply *data;
+ krb5_error_code ret;
+
+ /* These are required fields. */
+ if (is_empty(credential_id)
+ || is_empty(cryptographic_challenge)
+ || is_empty(authenticator_data)
+ || is_empty(assertion_signature)) {
+ return NULL;
+ }
+
+ data = malloc(sizeof(struct sss_passkey_reply));
+ if (data == NULL) {
+ return NULL;
+ }
+ memset(data, 0, sizeof(struct sss_passkey_reply));
+
+ data->credential_id = strdup(credential_id);
+ data->cryptographic_challenge = strdup(cryptographic_challenge);
+ data->authenticator_data = strdup(authenticator_data);
+ data->assertion_signature = strdup(assertion_signature);
+ data->user_id = user_id == NULL ? NULL : strdup(user_id);
+ if (data->credential_id == NULL
+ || data->cryptographic_challenge == NULL
+ || data->authenticator_data == NULL
+ || data->assertion_signature == NULL
+ || (user_id != NULL && data->user_id == NULL)) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ ret = 0;
+
+done:
+ if (ret != 0) {
+ sss_passkey_reply_free(data);
+ return NULL;
+ }
+
+ return data;
+}
+
+static struct sss_passkey_reply *
+sss_passkey_reply_from_json_object(json_t *jobject)
+{
+ struct sss_passkey_reply jdata = {0};
+ int ret;
+
+ if (jobject == NULL) {
+ return NULL;
+ }
+
+ ret = json_unpack(jobject, "{s:s, s:s, s:s, s:s, s?:s}",
+ "credential_id", &jdata.credential_id,
+ "cryptographic_challenge", &jdata.cryptographic_challenge,
+ "authenticator_data", &jdata.authenticator_data,
+ "assertion_signature", &jdata.assertion_signature,
+ "user_id", &jdata.user_id);
+ if (ret != 0) {
+ return NULL;
+ }
+
+ return sss_passkey_reply_init(jdata.credential_id,
+ jdata.cryptographic_challenge,
+ jdata.authenticator_data,
+ jdata.assertion_signature,
+ jdata.user_id);
+}
+
+static json_t *
+sss_passkey_reply_to_json_object(const struct sss_passkey_reply *data)
+{
+ if (data == NULL) {
+ return NULL;
+ }
+
+ /* These are required fields. */
+ if (data->credential_id == NULL
+ || data->cryptographic_challenge == NULL
+ || data->authenticator_data == NULL
+ || data->assertion_signature == NULL) {
+ return NULL;
+ }
+
+ return json_pack("{s:s, s:s, s:s, s:s, s:s*}",
+ "credential_id", data->credential_id,
+ "cryptographic_challenge", data->cryptographic_challenge,
+ "authenticator_data", data->authenticator_data,
+ "assertion_signature", data->assertion_signature,
+ "user_id", data->user_id);
+}
+
+void
+sss_passkey_message_free(struct sss_passkey_message *message)
+{
+ if (message == NULL) {
+ return;
+ }
+
+ switch (message->phase) {
+ case SSS_PASSKEY_PHASE_INIT:
+ break;
+ case SSS_PASSKEY_PHASE_CHALLENGE:
+ sss_passkey_challenge_free(message->data.challenge);
+ break;
+ case SSS_PASSKEY_PHASE_REPLY:
+ sss_passkey_reply_free(message->data.reply);
+ break;
+ default:
+ /* nothing to do */
+ break;
+ }
+
+ free(message->state);
+ free(message);
+}
+
+static struct sss_passkey_message *
+sss_passkey_message_init(enum sss_passkey_phase phase,
+ const char *state,
+ void *data)
+{
+ struct sss_passkey_message *message;
+
+ switch (phase) {
+ case SSS_PASSKEY_PHASE_INIT:
+ if (state != NULL || data != NULL) {
+ return NULL;
+ }
+ break;
+ case SSS_PASSKEY_PHASE_CHALLENGE:
+ case SSS_PASSKEY_PHASE_REPLY:
+ if (state == NULL || data == NULL) {
+ return NULL;
+ }
+ break;
+ default:
+ return NULL;
+ }
+
+ message = malloc(sizeof(struct sss_passkey_message));
+ if (message == NULL) {
+ return NULL;
+ }
+ memset(message, 0, sizeof(struct sss_passkey_message));
+
+ message->phase = phase;
+ message->state = state == NULL ? NULL : strdup(state);
+ message->data.ptr = data;
+
+ if (state != NULL && message->state == NULL) {
+ sss_passkey_message_free(message);
+ return NULL;
+ }
+
+ return message;
+}
+
+static struct sss_passkey_message *
+sss_passkey_message_from_json(const char *json_str)
+{
+ struct sss_passkey_message *message = NULL;
+ enum sss_passkey_phase phase = 0;
+ const char *state = NULL;
+ void *data = NULL;
+ json_error_t jret;
+ json_t *jdata = NULL;
+ json_t *jroot;
+ int ret;
+
+ jroot = json_loads(json_str, 0, &jret);
+ if (jroot == NULL) {
+ return NULL;
+ }
+
+ ret = json_unpack(jroot, "{s:i, s?:s, s?:o}",
+ "phase", &phase,
+ "state", &state,
+ "data", &jdata);
+ if (ret != 0) {
+ goto done;
+ }
+
+ switch (phase) {
+ case SSS_PASSKEY_PHASE_INIT:
+ data = NULL;
+ break;
+ case SSS_PASSKEY_PHASE_CHALLENGE:
+ data = sss_passkey_challenge_from_json_object(jdata);
+ if (data == NULL) {
+ goto done;
+ }
+ break;
+ case SSS_PASSKEY_PHASE_REPLY:
+ data = sss_passkey_reply_from_json_object(jdata);
+ if (data == NULL) {
+ goto done;
+ }
+ break;
+ default:
+ goto done;
+ }
+
+ message = sss_passkey_message_init(phase, state, data);
+ if (message == NULL && phase == SSS_PASSKEY_PHASE_CHALLENGE) {
+ sss_passkey_challenge_free(data);
+ } else if (message == NULL && phase == SSS_PASSKEY_PHASE_REPLY) {
+ sss_passkey_reply_free(data);
+ }
+
+done:
+ json_decref(jroot);
+ return message;
+}
+
+static char *
+sss_passkey_message_to_json(const struct sss_passkey_message *message)
+{
+ json_t *jroot;
+ json_t *jdata;
+ char *str;
+
+ if (message == NULL) {
+ return NULL;
+ }
+
+ switch (message->phase) {
+ case SSS_PASSKEY_PHASE_INIT:
+ if (message->state != NULL || message->data.ptr != NULL) {
+ return NULL;
+ }
+ jdata = NULL;
+ break;
+ case SSS_PASSKEY_PHASE_CHALLENGE:
+ if (message->state == NULL || message->data.challenge == NULL) {
+ return NULL;
+ }
+
+ jdata = sss_passkey_challenge_to_json_object(message->data.challenge);
+ if (jdata == NULL) {
+ return NULL;
+ }
+ break;
+ case SSS_PASSKEY_PHASE_REPLY:
+ if (message->state == NULL || message->data.reply == NULL) {
+ return NULL;
+ }
+
+ jdata = sss_passkey_reply_to_json_object(message->data.reply);
+ if (jdata == NULL) {
+ return NULL;
+ }
+ break;
+ default:
+ return NULL;
+ }
+
+ jroot = json_pack("{s:i, s:s*, s:o*}",
+ "phase", message->phase,
+ "state", message->state,
+ "data", jdata);
+ if (jroot == NULL) {
+ json_decref(jdata);
+ return NULL;
+ }
+
+ str = json_dumps(jroot, JSON_COMPACT);
+ json_decref(jroot);
+
+ return str;
+}
+
+struct sss_passkey_message *
+sss_passkey_message_from_reply_json(enum sss_passkey_phase phase,
+ const char *state,
+ const char *json_str)
+{
+ json_error_t jret;
+ json_t *jroot;
+ struct sss_passkey_message *message;
+ struct sss_passkey_reply *data;
+
+ if (json_str == NULL) {
+ return NULL;
+ }
+
+ jroot = json_loads(json_str, 0, &jret);
+ if (jroot == NULL) {
+ return NULL;
+ }
+
+ data = sss_passkey_reply_from_json_object(jroot);
+ if (data == NULL) {
+ json_decref(jroot);
+ return NULL;
+ }
+
+ message = sss_passkey_message_init(phase, state, data);
+ if (message == NULL) {
+ sss_passkey_reply_free(data);
+ }
+
+ json_decref(jroot);
+ return message;
+}
+
+char *
+sss_passkey_message_encode(const struct sss_passkey_message *data)
+{
+ return sss_radius_message_encode(SSSD_PASSKEY_PREFIX,
+ (sss_radius_message_encode_fn)sss_passkey_message_to_json, data);
+}
+
+struct sss_passkey_message *
+sss_passkey_message_decode(const char *str)
+{
+ return sss_radius_message_decode(SSSD_PASSKEY_PREFIX,
+ (sss_radius_message_decode_fn)sss_passkey_message_from_json, str);
+}
+
+krb5_pa_data *
+sss_passkey_message_encode_padata(const struct sss_passkey_message *data)
+{
+ return sss_radius_encode_padata(SSSD_PASSKEY_PADATA,
+ (sss_radius_message_encode_fn)sss_passkey_message_encode, data);
+}
+
+struct sss_passkey_message *
+sss_passkey_message_decode_padata(krb5_pa_data *padata)
+{
+ return sss_radius_decode_padata(
+ (sss_radius_message_decode_fn)sss_passkey_message_decode, padata);
+}
+
+krb5_pa_data **
+sss_passkey_message_encode_padata_array(const struct sss_passkey_message *data)
+{
+ return sss_radius_encode_padata_array(SSSD_PASSKEY_PADATA,
+ (sss_radius_message_encode_fn)sss_passkey_message_encode, data);
+}
+
+krb5_error_code
+sss_passkey_concat_credentials(char **creds,
+ char **_creds_str)
+{
+ krb5_error_code ret;
+ char *result_creds = NULL;
+ size_t total_sz = 0;
+ size_t len = 0;
+ int rc = 0;
+
+ for (int i = 0; creds[i] != NULL; i++) {
+ total_sz += strlen(creds[i]);
+ if (i > 0) {
+ /* separating comma in resulting creds string */
+ total_sz++;
+ }
+ }
+
+ result_creds = malloc(total_sz + 1);
+ if (result_creds == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ len = strlen(creds[0]);
+
+ rc = snprintf(result_creds, len + 1, "%s", creds[0]);
+ if (rc < 0 || rc > len) {
+ ret = ENOMEM;
+ free(result_creds);
+ goto done;
+ }
+
+ for (int i = 1; creds[i] != NULL; i++) {
+ rc = snprintf(result_creds + len, total_sz - len + 1, ",%s", creds[i]);
+ if (rc < 0 || rc > total_sz - len) {
+ ret = ENOMEM;
+ free(result_creds);
+ goto done;
+ }
+
+ len += rc;
+ }
+
+ *_creds_str = result_creds;
+
+ ret = 0;
+done:
+ return ret;
+}
diff --git a/src/krb5_plugin/passkey/sssd_enable_passkey b/src/krb5_plugin/passkey/sssd_enable_passkey
new file mode 100644
index 0000000..161e076
--- /dev/null
+++ b/src/krb5_plugin/passkey/sssd_enable_passkey
@@ -0,0 +1,14 @@
+# Enable SSSD Passkey Kerberos preauthentication plugins.
+#
+# This will allow you to obtain Kerberos TGT through passkey authentication.
+#
+# To disable the passkey plugin, comment out the following lines.
+
+[plugins]
+ clpreauth = {
+ module = passkey:/usr/lib64/sssd/modules/sssd_krb5_passkey_plugin.so
+ }
+
+ kdcpreauth = {
+ module = passkey:/usr/lib64/sssd/modules/sssd_krb5_passkey_plugin.so
+ }
diff --git a/src/krb5_plugin/sssd_krb5_localauth_plugin.c b/src/krb5_plugin/sssd_krb5_localauth_plugin.c
new file mode 100644
index 0000000..1d995ff
--- /dev/null
+++ b/src/krb5_plugin/sssd_krb5_localauth_plugin.c
@@ -0,0 +1,196 @@
+/*
+ Authors:
+ Sumit Bose <sbose@redhat.com>
+
+ Copyright (C) 2014 Red Hat
+
+ 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 <nss.h>
+#include <sys/types.h>
+#include <pwd.h>
+#include <string.h>
+#include <strings.h>
+#include <errno.h>
+
+#include <krb5/localauth_plugin.h>
+
+enum nss_status _nss_sss_getpwnam_r(const char *name, struct passwd *result,
+ char *buffer, size_t buflen, int *errnop);
+
+#define DEFAULT_BUFSIZE 4096
+
+static krb5_error_code sss_userok(krb5_context context,
+ krb5_localauth_moddata data,
+ krb5_const_principal aname,
+ const char *lname)
+{
+ krb5_error_code kerr;
+ char *princ_str;
+ struct passwd pwd = { 0 };
+ char *buffer = NULL;
+ size_t buflen;
+ enum nss_status nss_status;
+ int nss_errno;
+ uid_t princ_uid;
+ int ret;
+ struct passwd *result = NULL;
+
+ kerr = krb5_unparse_name(context, aname, &princ_str);
+ if (kerr != 0) {
+ ret = kerr;
+ goto done;
+ }
+
+ if (strcasecmp(princ_str, lname) == 0) {
+ ret = 0;
+ goto done;
+ }
+
+ buflen = DEFAULT_BUFSIZE;
+ buffer = malloc(buflen);
+ if (buffer == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ nss_status = _nss_sss_getpwnam_r(princ_str, &pwd, buffer, buflen,
+ &nss_errno);
+ if (nss_status != NSS_STATUS_SUCCESS) {
+ if (nss_status == NSS_STATUS_NOTFOUND) {
+ ret = KRB5_PLUGIN_NO_HANDLE;
+ } else {
+ ret = EIO;
+ }
+ goto done;
+ }
+
+ princ_uid = pwd.pw_uid;
+
+ ret = getpwnam_r(lname, &pwd, buffer, buflen, &result);
+ if (ret != 0 || result == NULL) {
+ if (result == NULL) {
+ ret = KRB5_PLUGIN_NO_HANDLE;
+ } else {
+ ret = EIO;
+ }
+ goto done;
+ }
+
+ if (princ_uid != pwd.pw_uid) {
+ ret = EPERM;
+ goto done;
+ }
+
+ ret = 0;
+
+done:
+ krb5_free_unparsed_name(context, princ_str);
+ free(buffer);
+
+ if (ret != 0) {
+ return KRB5_PLUGIN_NO_HANDLE;
+ }
+
+ return ret;
+}
+
+static krb5_error_code sss_an2ln(krb5_context context,
+ krb5_localauth_moddata data,
+ const char *type, const char *residual,
+ krb5_const_principal aname, char **lname_out)
+{
+ krb5_error_code kerr;
+ char *princ_str;
+ struct passwd pwd = { 0 };
+ char *buffer = NULL;
+ size_t buflen;
+ enum nss_status nss_status;
+ int nss_errno;
+ int ret;
+ char *str;
+
+ kerr = krb5_unparse_name(context, aname, &princ_str);
+ if (kerr != 0) {
+ return kerr;
+ }
+
+ buflen = DEFAULT_BUFSIZE;
+ buffer = malloc(buflen);
+ if (buffer == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ nss_status = _nss_sss_getpwnam_r(princ_str, &pwd, buffer, buflen,
+ &nss_errno);
+ if (nss_status != NSS_STATUS_SUCCESS) {
+ if (nss_status == NSS_STATUS_NOTFOUND) {
+ ret = KRB5_LNAME_NOTRANS;
+ } else {
+ ret = EIO;
+ }
+ goto done;
+ }
+
+ if (pwd.pw_name == NULL) {
+ ret = EINVAL;
+ goto done;
+ }
+
+ str = strdup(pwd.pw_name);
+ if (str == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ *lname_out = str;
+
+ ret = 0;
+
+done:
+ krb5_free_unparsed_name(context, princ_str);
+ free(buffer);
+
+ return ret;
+}
+
+static void sss_freestr(krb5_context context,
+ krb5_localauth_moddata data, char *str)
+{
+ free(str);
+}
+
+krb5_error_code
+localauth_sssd_initvt(krb5_context context, int maj_ver, int min_ver,
+ krb5_plugin_vtable vtable)
+{
+
+ if (maj_ver != 1 || min_ver != 1) {
+ return KRB5_PLUGIN_VER_NOTSUPP;
+ }
+
+ krb5_localauth_vtable vt = (krb5_localauth_vtable)vtable;
+
+ vt->init = NULL;
+ vt->fini = NULL;
+ vt->name = "sssd";
+ vt->an2ln = sss_an2ln;
+ vt->userok = sss_userok;
+ vt->free_string = sss_freestr;
+
+ return 0;
+}
+
diff --git a/src/krb5_plugin/sssd_krb5_locator_plugin.c b/src/krb5_plugin/sssd_krb5_locator_plugin.c
new file mode 100644
index 0000000..936815e
--- /dev/null
+++ b/src/krb5_plugin/sssd_krb5_locator_plugin.c
@@ -0,0 +1,646 @@
+/*
+ Authors:
+ Sumit Bose <sbose@redhat.com>
+
+ Copyright (C) 2009 Red Hat
+
+ 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 "config.h"
+
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <netdb.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <ctype.h>
+
+#include "util/sss_krb5.h"
+#include <krb5/locate_plugin.h>
+
+#include "providers/krb5/krb5_common.h"
+
+/* The following override of KDCINFO_TMPL and KPASSWDINFO_TMPL is not very
+ * elegant but since they are defined in krb5_common.h with the help of
+ * PUBCONF_PATH from config.h and PUBCONF_PATH can by set by a configure
+ * options I didn't found another way to change the path for a unit test. */
+#ifdef TEST_PUBCONF_PATH
+#ifdef KDCINFO_TMPL
+#undef KDCINFO_TMPL
+#endif
+#define KDCINFO_TMPL TEST_PUBCONF_PATH"/kdcinfo.%s"
+
+#ifdef KPASSWDINFO_TMPL
+#undef KPASSWDINFO_TMPL
+#endif
+#define KPASSWDINFO_TMPL TEST_PUBCONF_PATH"/kpasswdinfo.%s"
+#endif /* TEST_PUBCONF_PATH */
+
+#define DEFAULT_KERBEROS_PORT 88
+#define DEFAULT_KADMIN_PORT 749
+#define DEFAULT_KPASSWD_PORT 464
+
+#define BUFSIZE 4096
+#define PORT_STR_SIZE 7
+#define SSSD_KRB5_LOCATOR_DEBUG "SSSD_KRB5_LOCATOR_DEBUG"
+#define SSSD_KRB5_LOCATOR_DISABLE "SSSD_KRB5_LOCATOR_DISABLE"
+#define SSSD_KRB5_LOCATOR_IGNORE_DNS_FAILURES "SSSD_KRB5_LOCATOR_IGNORE_DNS_FAILURES"
+#define DEBUG_KEY "[sssd_krb5_locator] "
+#define PLUGIN_DEBUG(format, ...) do { \
+ if (ctx->debug) { \
+ plugin_debug_fn(format, ##__VA_ARGS__); \
+ } \
+} while(0)
+
+struct addr_port {
+ char *addr;
+ uint16_t port;
+};
+
+struct sssd_ctx {
+ char *sssd_realm;
+ struct addr_port *kdc_addr;
+ struct addr_port *kpasswd_addr;
+ bool debug;
+ bool disabled;
+ bool kpasswdinfo_used;
+ bool ignore_dns_failure;
+};
+
+#ifdef HAVE_FUNCTION_ATTRIBUTE_FORMAT
+__attribute__((format(printf, 1, 2)))
+#endif
+static void plugin_debug_fn(const char *format, ...)
+{
+ va_list ap;
+ char *s = NULL;
+ int ret;
+
+ va_start(ap, format);
+
+ ret = vasprintf(&s, format, ap);
+ va_end(ap);
+ if (ret < 0) {
+ /* ENOMEM */
+ return;
+ }
+
+ fprintf(stderr, DEBUG_KEY "%s", s);
+ free(s);
+}
+
+
+static void free_addr_port_list(struct addr_port **list)
+{
+ size_t c;
+
+ if (list == NULL || *list == NULL) {
+ return;
+ }
+
+ for (c = 0; (*list)[c].addr != NULL; c++) {
+ free((*list)[c].addr);
+ }
+ free(*list);
+ *list = NULL;
+}
+
+static int copy_addr_port_list(struct addr_port *src, bool clear_port,
+ struct addr_port **dst)
+{
+ size_t c;
+ struct addr_port *d = NULL;
+ int ret;
+
+ /* only copy if dst is initialized to NULL */
+ if (dst == NULL || *dst != NULL) {
+ return EINVAL;
+ }
+
+ if (src == NULL) {
+ return 0;
+ }
+
+ for (c = 0; src[c].addr != NULL; c++);
+
+ d = calloc((c + 1), sizeof(struct addr_port));
+ if (d == NULL) {
+ return ENOMEM;
+ }
+
+ for (c = 0; src[c].addr != NULL; c++) {
+ d[c].addr = strdup(src[c].addr);
+ if (d[c].addr == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+ if (clear_port) {
+ d[c].port = 0;
+ } else {
+ d[c].port = src[c].port;
+ }
+ }
+
+ ret = EOK;
+
+done:
+ if (ret != EOK) {
+ free_addr_port_list(&d);
+ } else {
+ *dst = d;
+ }
+
+ return ret;
+}
+
+static int buf_to_addr_port_list(struct sssd_ctx *ctx,
+ uint8_t *buf, size_t buf_size,
+ struct addr_port **list)
+{
+ struct addr_port *l = NULL;
+ int ret;
+ uint8_t *p;
+ uint8_t *pn;
+ size_t c;
+ size_t len;
+ size_t addr_len;
+ char *addr_str = NULL;
+ char *tmp = NULL;
+ char *port_str;
+ long port;
+ char *endptr;
+
+ /* only create if list is initialized to NULL */
+ if (buf == NULL || buf_size == 0 || list == NULL || *list != NULL) {
+ return EINVAL;
+ }
+
+ c = 1; /* to account for a missing \n at the very end */
+ p = buf;
+ while ((p - buf) < buf_size
+ && (p = memchr(p, '\n', buf_size - (p - buf))) != NULL) {
+ p++;
+ c++;
+ }
+
+ l = calloc((c + 1), sizeof(struct addr_port));
+ if (l == NULL) {
+ return ENOMEM;
+ }
+
+ c = 0;
+ p = buf;
+ do {
+ pn = memchr(p, '\n', buf_size - (p - buf));
+ if (pn != NULL) {
+ len = pn - p;
+ } else {
+ len = buf_size - (p - buf);
+ }
+ if (len == 0) {
+ /* empty line no more processing */
+ break;
+ }
+
+ free(tmp);
+ tmp = strndup((char *) p, len);
+ if (tmp == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+
+ port_str = strrchr(tmp, ':');
+ if (port_str == NULL) {
+ port = 0;
+ } else if (tmp[0] == '[' && *(port_str - 1) != ']') {
+ /* IPv6 address without port number */
+ port = 0;
+ } else {
+ *port_str = '\0';
+ ++port_str;
+
+ if (isdigit(*port_str)) {
+ errno = 0;
+ port = strtol(port_str, &endptr, 10);
+ if (errno != 0) {
+ ret = errno;
+ PLUGIN_DEBUG("strtol failed on [%s]: [%d][%s], "
+ "assuming default.\n",
+ port_str, ret, strerror(ret));
+ port = 0;
+ }
+ if (*endptr != '\0') {
+ PLUGIN_DEBUG("Found additional characters [%s] in port "
+ "number [%s], assuming default.\n",
+ endptr, port_str);
+ port = 0;
+ }
+
+ if (port < 0 || port > 65535) {
+ PLUGIN_DEBUG("Illegal port number [%ld], assuming "
+ "default.\n", port);
+ port = 0;
+ }
+ } else {
+ PLUGIN_DEBUG("Illegal port number [%s], assuming default.\n",
+ port_str);
+ port = 0;
+ }
+ }
+
+ /* make sure tmp is not modified so that it can be freed later */
+ addr_str = tmp;
+ /* strip leading '[' and trailing ']' from IPv6 addresses */
+ if (addr_str[0] == '['
+ && (addr_len = strlen(addr_str))
+ && addr_str[addr_len - 1] == ']') {
+ addr_str[addr_len -1] = '\0';
+ addr_str++;
+ }
+
+ PLUGIN_DEBUG("Found [%s][%ld].\n", addr_str, port);
+
+ l[c].addr = strdup(addr_str);
+ if (l[c].addr == NULL) {
+ ret = ENOMEM;
+ goto done;
+ }
+ l[c].port = port;
+
+ c++;
+ p = pn == NULL ? NULL : (pn + 1);
+ } while (p != NULL);
+
+ ret = EOK;
+
+done:
+ free(tmp);
+ if (ret != EOK) {
+ free_addr_port_list(&l);
+ } else {
+ *list = l;
+ }
+
+ return ret;
+}
+
+static int get_krb5info(const char *realm, struct sssd_ctx *ctx,
+ enum locate_service_type svc)
+{
+ int ret;
+ char *krb5info_name = NULL;
+ size_t len;
+ uint8_t buf[BUFSIZE + 1];
+ int fd = -1;
+ const char *name_tmpl = NULL;
+
+ switch (svc) {
+ case locate_service_kdc:
+ name_tmpl = KDCINFO_TMPL;
+ break;
+ case locate_service_kpasswd:
+ name_tmpl = KPASSWDINFO_TMPL;
+ break;
+ default:
+ PLUGIN_DEBUG("Unsupported service [%d].\n", svc);
+ return EINVAL;
+ }
+
+
+ len = strlen(realm) + strlen(name_tmpl);
+
+ krb5info_name = calloc(1, len + 1);
+ if (krb5info_name == NULL) {
+ PLUGIN_DEBUG("calloc failed.\n");
+ return ENOMEM;
+ }
+
+ ret = snprintf(krb5info_name, len, name_tmpl, realm);
+ if (ret < 0) {
+ PLUGIN_DEBUG("snprintf failed.\n");
+ ret = EINVAL;
+ goto done;
+ }
+ krb5info_name[len] = '\0';
+
+ fd = open(krb5info_name, O_RDONLY);
+ if (fd == -1) {
+ ret = errno;
+ PLUGIN_DEBUG("open failed [%s][%d][%s].\n",
+ krb5info_name, ret, strerror(ret));
+ goto done;
+ }
+
+ memset(buf, 0, BUFSIZE+1);
+
+ errno = 0;
+ len = sss_atomic_read_s(fd, buf, BUFSIZE);
+ if (len == -1) {
+ ret = errno;
+ PLUGIN_DEBUG("read failed [%d][%s].\n", ret, strerror(ret));
+ close(fd);
+ goto done;
+ }
+ close(fd);
+
+ if (len == BUFSIZE) {
+ PLUGIN_DEBUG("Content of krb5info file [%s] is [%d] or larger.\n",
+ krb5info_name, BUFSIZE);
+ }
+
+ switch (svc) {
+ case locate_service_kdc:
+ free_addr_port_list(&(ctx->kdc_addr));
+ ret = buf_to_addr_port_list(ctx, buf, len, &(ctx->kdc_addr));
+ if (ret != EOK) {
+ goto done;
+ }
+ break;
+ case locate_service_kpasswd:
+ free_addr_port_list(&(ctx->kpasswd_addr));
+ ret = buf_to_addr_port_list(ctx, buf, len, &(ctx->kpasswd_addr));
+ if (ret != EOK) {
+ goto done;
+ }
+ break;
+ default:
+ PLUGIN_DEBUG("Unsupported service [%d].\n", svc);
+ ret = EINVAL;
+ goto done;
+ }
+
+ ret = 0;
+done:
+ free(krb5info_name);
+ return ret;
+}
+
+krb5_error_code sssd_krb5_locator_init(krb5_context context,
+ void **private_data)
+{
+ struct sssd_ctx *ctx;
+ const char *dummy;
+
+ ctx = calloc(1,sizeof(struct sssd_ctx));
+ if (ctx == NULL) return KRB5_PLUGIN_NO_HANDLE;
+
+ dummy = getenv(SSSD_KRB5_LOCATOR_DEBUG);
+ if (dummy == NULL) {
+ ctx->debug = false;
+ } else {
+ ctx->debug = true;
+ PLUGIN_DEBUG("sssd_krb5_locator_init called\n");
+ }
+
+ dummy = getenv(SSSD_KRB5_LOCATOR_DISABLE);
+ if (dummy == NULL) {
+ ctx->disabled = false;
+ } else {
+ ctx->disabled = true;
+ PLUGIN_DEBUG("SSSD KRB5 locator plugin is disabled.\n");
+ }
+
+ ctx->kpasswdinfo_used = false;
+
+ dummy = getenv(SSSD_KRB5_LOCATOR_IGNORE_DNS_FAILURES);
+ if (dummy == NULL) {
+ ctx->ignore_dns_failure = false;
+ } else {
+ ctx->ignore_dns_failure = true;
+ PLUGIN_DEBUG("SSSD KRB5 locator plugin ignores DNS resolving errors.\n");
+ }
+
+ *private_data = ctx;
+
+ return 0;
+}
+
+void sssd_krb5_locator_close(void *private_data)
+{
+ struct sssd_ctx *ctx;
+
+ if (private_data == NULL) return;
+
+ ctx = (struct sssd_ctx *) private_data;
+ PLUGIN_DEBUG("sssd_krb5_locator_close called\n");
+
+ free_addr_port_list(&(ctx->kdc_addr));
+ free_addr_port_list(&(ctx->kpasswd_addr));
+ free(ctx->sssd_realm);
+ free(ctx);
+
+ return;
+}
+
+krb5_error_code sssd_krb5_locator_lookup(void *private_data,
+ enum locate_service_type svc,
+ const char *realm,
+ int socktype,
+ int family,
+ int (*cbfunc)(void *, int, struct sockaddr *),
+ void *cbdata)
+{
+ int ret;
+ struct addrinfo *ai, *ai_item;
+ struct sssd_ctx *ctx;
+ struct addrinfo ai_hints;
+ uint16_t port = 0;
+ uint16_t default_port = 0;
+ struct addr_port *addr = NULL;
+ char port_str[PORT_STR_SIZE];
+ size_t c;
+ bool force_port = false;
+ char address[NI_MAXHOST];
+
+ if (private_data == NULL) return KRB5_PLUGIN_NO_HANDLE;
+ ctx = (struct sssd_ctx *) private_data;
+
+ if (realm == NULL || cbfunc == NULL || cbdata == NULL) {
+ return KRB5_PLUGIN_NO_HANDLE;
+ }
+
+ if (ctx->disabled) {
+ PLUGIN_DEBUG("Plugin disabled, nothing to do.\n");
+ return KRB5_PLUGIN_NO_HANDLE;
+ }
+
+ if (ctx->sssd_realm == NULL || strcmp(ctx->sssd_realm, realm) != 0) {
+ free(ctx->sssd_realm);
+ ctx->sssd_realm = strdup(realm);
+ if (ctx->sssd_realm == NULL) {
+ PLUGIN_DEBUG("strdup failed.\n");
+ return KRB5_PLUGIN_NO_HANDLE;
+ }
+
+ ret = get_krb5info(realm, ctx, locate_service_kdc);
+ if (ret != EOK) {
+ PLUGIN_DEBUG("get_krb5info failed.\n");
+ return KRB5_PLUGIN_NO_HANDLE;
+ }
+
+ }
+
+ if (ctx->kpasswd_addr == NULL
+ && (svc == locate_service_kadmin || svc == locate_service_kpasswd ||
+ svc == locate_service_master_kdc)) {
+ ret = get_krb5info(realm, ctx, locate_service_kpasswd);
+ if (ret != EOK) {
+ PLUGIN_DEBUG("reading kpasswd address failed, "
+ "using kdc address.\n");
+ free_addr_port_list(&(ctx->kpasswd_addr));
+ ret = copy_addr_port_list(ctx->kdc_addr, true,
+ &(ctx->kpasswd_addr));
+ if (ret != EOK) {
+ PLUGIN_DEBUG("copying address list failed.\n");
+ return KRB5_PLUGIN_NO_HANDLE;
+ }
+ } else {
+ ctx->kpasswdinfo_used = true;
+ }
+ }
+
+ PLUGIN_DEBUG("sssd_realm[%s] requested realm[%s] family[%d] socktype[%d] "
+ "locate_service[%d]\n",
+ ctx->sssd_realm, realm, family, socktype, svc);
+
+ switch (svc) {
+ case locate_service_kdc:
+ addr = ctx->kdc_addr;
+ default_port = DEFAULT_KERBEROS_PORT;
+ break;
+ case locate_service_master_kdc:
+ addr = ctx->kpasswd_addr;
+ default_port = DEFAULT_KERBEROS_PORT;
+ if (ctx->kpasswdinfo_used) {
+ /* Use default port if the addresses from the kpasswdinfo
+ * files are used because the port numbers from the file will
+ * most probably not be suitable. */
+ force_port = true;
+ }
+ break;
+ case locate_service_kadmin:
+ addr = ctx->kpasswd_addr;
+ default_port = DEFAULT_KADMIN_PORT;
+ break;
+ case locate_service_kpasswd:
+ addr = ctx->kpasswd_addr;
+ default_port = DEFAULT_KPASSWD_PORT;
+ break;
+ case locate_service_krb524:
+ return KRB5_PLUGIN_NO_HANDLE;
+ default:
+ return KRB5_PLUGIN_NO_HANDLE;
+ }
+
+ switch (family) {
+ case AF_UNSPEC:
+ case AF_INET:
+ case AF_INET6:
+ break;
+ default:
+ return KRB5_PLUGIN_NO_HANDLE;
+ }
+
+ switch (socktype) {
+ case SOCK_STREAM:
+ case SOCK_DGRAM:
+ break;
+ default:
+ return KRB5_PLUGIN_NO_HANDLE;
+ }
+
+ if (strcmp(realm, ctx->sssd_realm) != 0 || addr == NULL) {
+ return KRB5_PLUGIN_NO_HANDLE;
+ }
+
+ for (c = 0; addr[c].addr != NULL; c++) {
+ port = ((addr[c].port == 0 || force_port) ? default_port
+ : addr[c].port);
+ memset(port_str, 0, PORT_STR_SIZE);
+ ret = snprintf(port_str, PORT_STR_SIZE-1, "%u", port);
+ if (ret < 0 || ret >= (PORT_STR_SIZE-1)) {
+ PLUGIN_DEBUG("snprintf failed.\n");
+ return KRB5_PLUGIN_NO_HANDLE;
+ }
+
+ memset(&ai_hints, 0, sizeof(struct addrinfo));
+ ai_hints.ai_flags = AI_NUMERICSERV;
+ ai_hints.ai_socktype = socktype;
+
+ ret = getaddrinfo(addr[c].addr, port_str, &ai_hints, &ai);
+ if (ret != 0) {
+ PLUGIN_DEBUG("getaddrinfo failed [%d][%s].\n",
+ ret, gai_strerror(ret));
+ if (ret == EAI_SYSTEM) {
+ PLUGIN_DEBUG("getaddrinfo failed [%d][%s].\n",
+ errno, strerror(errno));
+ }
+
+ if (ctx->ignore_dns_failure) {
+ continue;
+ }
+
+ return KRB5_PLUGIN_NO_HANDLE;
+ }
+
+ for (ai_item = ai; ai_item != NULL; ai_item = ai_item->ai_next) {
+ if (ctx->debug) {
+ ret = getnameinfo(ai_item->ai_addr, ai_item->ai_addrlen,
+ address, NI_MAXHOST,
+ NULL, 0,
+ NI_NUMERICHOST);
+ if (ret != 0) {
+ address[0] = 0;
+ }
+
+ PLUGIN_DEBUG("addr[%s (%s)] port[%s] family[%d] socktype[%d]\n",
+ addr[c].addr, address,
+ port_str, ai_item->ai_family,
+ ai_item->ai_socktype);
+ }
+
+ if ((family == AF_UNSPEC || ai_item->ai_family == family) &&
+ ai_item->ai_socktype == socktype) {
+
+ ret = cbfunc(cbdata, socktype, ai_item->ai_addr);
+ if (ret != 0) {
+ PLUGIN_DEBUG("cbfunc failed\n");
+ freeaddrinfo(ai);
+ return ret;
+ } else {
+ PLUGIN_DEBUG("[%s (%s)] used\n", addr[c].addr, address);
+ }
+ } else {
+ PLUGIN_DEBUG("[%s (%s)] NOT used\n", addr[c].addr, address);
+ }
+ }
+ freeaddrinfo(ai);
+ }
+
+ return 0;
+}
+
+const krb5plugin_service_locate_ftable service_locator = {
+ 0, /* version */
+ sssd_krb5_locator_init,
+ sssd_krb5_locator_close,
+ sssd_krb5_locator_lookup,
+};