summaryrefslogtreecommitdiffstats
path: root/src/krb5_plugin/passkey
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/passkey
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/passkey')
-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
5 files changed, 1605 insertions, 0 deletions
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
+ }