summaryrefslogtreecommitdiffstats
path: root/src/krb5_plugin/idp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 05:31:45 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 05:31:45 +0000
commit74aa0bc6779af38018a03fd2cf4419fe85917904 (patch)
tree9cb0681aac9a94a49c153d5823e7a55d1513d91f /src/krb5_plugin/idp
parentInitial commit. (diff)
downloadsssd-74aa0bc6779af38018a03fd2cf4419fe85917904.tar.xz
sssd-74aa0bc6779af38018a03fd2cf4419fe85917904.zip
Adding upstream version 2.9.4.upstream/2.9.4
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/krb5_plugin/idp')
-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
5 files changed, 991 insertions, 0 deletions
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
+ }