diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 05:31:45 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 05:31:45 +0000 |
commit | 74aa0bc6779af38018a03fd2cf4419fe85917904 (patch) | |
tree | 9cb0681aac9a94a49c153d5823e7a55d1513d91f /src/p11_child | |
parent | Initial commit. (diff) | |
download | sssd-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/p11_child')
-rw-r--r-- | src/p11_child/p11_child.h | 77 | ||||
-rw-r--r-- | src/p11_child/p11_child_common.c | 403 | ||||
-rw-r--r-- | src/p11_child/p11_child_common_utils.c | 241 | ||||
-rw-r--r-- | src/p11_child/p11_child_openssl.c | 2140 |
4 files changed, 2861 insertions, 0 deletions
diff --git a/src/p11_child/p11_child.h b/src/p11_child/p11_child.h new file mode 100644 index 0000000..e3547ce --- /dev/null +++ b/src/p11_child/p11_child.h @@ -0,0 +1,77 @@ +/* + SSSD + + Helper child to commmunicate with SmartCard + + Authors: + Sumit Bose <sbose@redhat.com> + + Copyright (C) 2018 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 __P11_CHILD_H__ +#define __P11_CHILD_H__ + +/* for CK_MECHANISM_TYPE */ +#include <p11-kit/pkcs11.h> + +/* Time to wait for new slot events. */ +#define PKCS11_SLOT_EVENT_WAIT_TIME 1 +struct p11_ctx; + +struct cert_verify_opts { + bool do_ocsp; + bool do_verification; + bool verification_partial_chain; + char *ocsp_default_responder; + char *ocsp_default_responder_signing_cert; + char **crl_files; + int num_files; + CK_MECHANISM_TYPE ocsp_dgst; + bool soft_ocsp; + bool soft_crl; +}; + +enum op_mode { + OP_NONE, + OP_AUTH, + OP_PREAUTH, + OP_VERIFIY +}; + +enum pin_mode { + PIN_NONE, + PIN_STDIN, + PIN_KEYPAD +}; + +errno_t init_p11_ctx(TALLOC_CTX *mem_ctx, const char *ca_db, + bool wait_for_card, struct p11_ctx **p11_ctx); + +errno_t init_verification(struct p11_ctx *p11_ctx, + struct cert_verify_opts *cert_verify_opts); + +bool do_verification_b64(struct p11_ctx *p11_ctx, const char *cert_b64); + +errno_t do_card(TALLOC_CTX *mem_ctx, struct p11_ctx *p11_ctx, + enum op_mode mode, const char *pin, + const char *module_name_in, const char *token_name_in, + const char *key_id_in, const char *label, + const char *uri, char **_multi); + +errno_t parse_cert_verify_opts(TALLOC_CTX *mem_ctx, const char *verify_opts, + struct cert_verify_opts **cert_verify_opts); +#endif /* __P11_CHILD_H__ */ diff --git a/src/p11_child/p11_child_common.c b/src/p11_child/p11_child_common.c new file mode 100644 index 0000000..5eab9b0 --- /dev/null +++ b/src/p11_child/p11_child_common.c @@ -0,0 +1,403 @@ +/* + SSSD + + Helper child to commmunicate with SmartCard -- common code + + Authors: + Sumit Bose <sbose@redhat.com> + + Copyright (C) 2019 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 <unistd.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <popt.h> +#include <sys/prctl.h> + +#include "util/util.h" +#include "util/child_common.h" +#include "providers/backend.h" +#include "util/crypto/sss_crypto.h" +#include "util/cert.h" +#include "util/sss_chain_id.h" +#include "p11_child/p11_child.h" + +static const char *op_mode_str(enum op_mode mode) +{ + switch (mode) { + case OP_NONE: + return "none"; + break; + case OP_AUTH: + return "auth"; + break; + case OP_PREAUTH: + return "pre-auth"; + break; + case OP_VERIFIY: + return "verify"; + break; + default: + return "unknown"; + } +} + +static int do_work(TALLOC_CTX *mem_ctx, enum op_mode mode, const char *ca_db, + struct cert_verify_opts *cert_verify_opts, + bool wait_for_card, + const char *cert_b64, const char *pin, + const char *module_name, const char *token_name, + const char *key_id, const char *label, const char *uri, + char **multi) +{ + int ret; + struct p11_ctx *p11_ctx; + + ret = init_p11_ctx(mem_ctx, ca_db, wait_for_card, &p11_ctx); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "init_p11_ctx failed.\n"); + return ret; + } + + if (cert_verify_opts->do_verification) { + ret = init_verification(p11_ctx, cert_verify_opts); + if (ret != 0) { + DEBUG(SSSDBG_OP_FAILURE, "init_verification failed.\n"); + goto done; + } + } + + + if (mode == OP_VERIFIY) { + if (!cert_verify_opts->do_verification + || do_verification_b64(p11_ctx, cert_b64)) { + DEBUG(SSSDBG_TRACE_FUNC, "Certificate is valid.\n"); + ret = 0; + } else { + DEBUG(SSSDBG_TRACE_FUNC, "Certificate is NOT valid.\n"); + ret = EINVAL; + } + } else { + ret = do_card(mem_ctx, p11_ctx, mode, pin, + module_name, token_name, key_id, label, uri, multi); + } + +done: + talloc_free(p11_ctx); + + return ret; +} + +static errno_t p11c_recv_data(TALLOC_CTX *mem_ctx, int fd, char **pin) +{ + uint8_t buf[IN_BUF_SIZE]; + ssize_t len; + errno_t ret; + char *str; + + errno = 0; + len = sss_atomic_read_s(fd, buf, IN_BUF_SIZE); + if (len == -1) { + ret = errno; + ret = (ret == 0) ? EINVAL: ret; + DEBUG(SSSDBG_CRIT_FAILURE, + "read failed [%d][%s].\n", ret, strerror(ret)); + return ret; + } + + if (len == 0 || *buf == '\0') { + DEBUG(SSSDBG_CRIT_FAILURE, "Missing PIN.\n"); + return EINVAL; + } + + str = talloc_strndup(mem_ctx, (char *) buf, len); + if (str == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_strndup failed.\n"); + return ENOMEM; + } + + if (strlen(str) != len) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Input contains additional data, only PIN expected.\n"); + talloc_free(str); + return EINVAL; + } + + *pin = str; + + return EOK; +} + +int main(int argc, const char *argv[]) +{ + int opt; + poptContext pc; + int dumpable = 1; + int debug_fd = -1; + const char *opt_logger = NULL; + errno_t ret = 0; + TALLOC_CTX *main_ctx = NULL; + enum op_mode mode = OP_NONE; + enum pin_mode pin_mode = PIN_NONE; + char *pin = NULL; + char *ca_db = NULL; + struct cert_verify_opts *cert_verify_opts; + char *verify_opts = NULL; + char *multi = NULL; + char *module_name = NULL; + char *token_name = NULL; + char *key_id = NULL; + char *label = NULL; + char *cert_b64 = NULL; + long chain_id = 0; + bool wait_for_card = false; + char *uri = NULL; + + struct poptOption long_options[] = { + POPT_AUTOHELP + SSSD_DEBUG_OPTS + {"dumpable", 0, POPT_ARG_INT, &dumpable, 0, + _("Allow core dumps"), NULL }, + {"debug-fd", 0, POPT_ARG_INT, &debug_fd, 0, + _("An open file descriptor for the debug logs"), NULL}, + SSSD_LOGGER_OPTS + {"auth", 0, POPT_ARG_NONE, NULL, 'a', _("Run in auth mode"), NULL}, + {"pre", 0, POPT_ARG_NONE, NULL, 'p', _("Run in pre-auth mode"), NULL}, + {"wait_for_card", 0, POPT_ARG_NONE, NULL, 'w', _("Wait until card is available"), NULL}, + {"verification", 0, POPT_ARG_NONE, NULL, 'v', _("Run in verification mode"), + NULL}, + {"pin", 0, POPT_ARG_NONE, NULL, 'i', _("Expect PIN on stdin"), NULL}, + {"keypad", 0, POPT_ARG_NONE, NULL, 'k', _("Expect PIN on keypad"), + NULL}, + {"verify", 0, POPT_ARG_STRING, &verify_opts, 0 , _("Tune validation"), + NULL}, + {"ca_db", 0, POPT_ARG_STRING, &ca_db, 0, _("CA DB to use"), + NULL}, + {"module_name", 0, POPT_ARG_STRING, &module_name, 0, + _("Module name for authentication"), NULL}, + {"token_name", 0, POPT_ARG_STRING, &token_name, 0, + _("Token name for authentication"), NULL}, + {"key_id", 0, POPT_ARG_STRING, &key_id, 0, + _("Key ID for authentication"), NULL}, + {"label", 0, POPT_ARG_STRING, &label, 0, + _("Label for authentication"), NULL}, + {"certificate", 0, POPT_ARG_STRING, &cert_b64, 0, + _("certificate to verify, base64 encoded"), NULL}, + {"uri", 0, POPT_ARG_STRING, &uri, 0, + _("PKCS#11 URI to restrict selection"), NULL}, + {"chain-id", 0, POPT_ARG_LONG, &chain_id, + 0, _("Tevent chain ID used for logging purposes"), NULL}, + POPT_TABLEEND + }; + + /* Set debug level to invalid value so we can decide if -d 0 was used. */ + debug_level = SSSDBG_INVALID; + + /* + * This child can run as root or as sssd user relying on policy kit to + * grant access to pcscd. This means that no setuid or setgid bit must be + * set on the binary. We still should make sure to run with a restrictive + * umask but do not have to make additional precautions like clearing the + * environment. This would allow to use e.g. pkcs11-spy.so for further + * debugging. + */ + umask(SSS_DFL_UMASK); + + pc = poptGetContext(argv[0], argc, argv, long_options, 0); + while ((opt = poptGetNextOpt(pc)) != -1) { + switch(opt) { + case 'a': + if (mode != OP_NONE) { + fprintf(stderr, + "\n--verify, --auth and --pre are mutually " \ + "exclusive and should be only used once.\n\n"); + poptPrintUsage(pc, stderr, 0); + _exit(-1); + } + mode = OP_AUTH; + break; + case 'p': + if (mode != OP_NONE) { + fprintf(stderr, + "\n--verify, --auth and --pre are mutually " \ + "exclusive and should be only used once.\n\n"); + poptPrintUsage(pc, stderr, 0); + _exit(-1); + } + mode = OP_PREAUTH; + break; + case 'v': + if (mode != OP_NONE) { + fprintf(stderr, + "\n--verify, --auth and --pre are mutually " \ + "exclusive and should be only used once.\n\n"); + poptPrintUsage(pc, stderr, 0); + _exit(-1); + } + mode = OP_VERIFIY; + break; + case 'i': + if (pin_mode != PIN_NONE) { + fprintf(stderr, "\n--pin and --keypad are mutually exclusive " \ + "and should be only used once.\n\n"); + poptPrintUsage(pc, stderr, 0); + _exit(-1); + } + pin_mode = PIN_STDIN; + break; + case 'k': + if (pin_mode != PIN_NONE) { + fprintf(stderr, "\n--pin and --keypad are mutually exclusive " \ + "and should be only used once.\n\n"); + poptPrintUsage(pc, stderr, 0); + _exit(-1); + } + pin_mode = PIN_KEYPAD; + break; + case 'w': + wait_for_card = true; + break; + default: + fprintf(stderr, "\nInvalid option %s: %s\n\n", + poptBadOption(pc, 0), poptStrerror(opt)); + poptPrintUsage(pc, stderr, 0); + _exit(-1); + } + } + + if (ca_db == NULL) { + fprintf(stderr, "\nMissing CA DB path: --ca_db must be specified.\n\n"); + poptPrintUsage(pc, stderr, 0); + _exit(-1); + } + + if (mode == OP_NONE) { + fprintf(stderr, "\nMissing operation mode, either " \ + "--verify, --auth or --pre must be specified.\n\n"); + poptPrintUsage(pc, stderr, 0); + _exit(-1); + } else if (mode == OP_AUTH && pin_mode == PIN_NONE) { + fprintf(stderr, "\nMissing PIN mode for authentication, " \ + "either --pin or --keypad must be specified.\n"); + poptPrintUsage(pc, stderr, 0); + _exit(-1); + } else if (mode == OP_VERIFIY && cert_b64 == NULL) { + fprintf(stderr, "\nMissing certificate for verify operation, " \ + "--certificate base64_encoded_certificate " \ + "must be added.\n"); + poptPrintUsage(pc, stderr, 0); + _exit(-1); + } + + poptFreeContext(pc); + + prctl(PR_SET_DUMPABLE, (dumpable == 0) ? 0 : 1); + + debug_prg_name = talloc_asprintf(NULL, "p11_child[%d]", getpid()); + if (debug_prg_name == NULL) { + ERROR("talloc_asprintf failed.\n"); + ret = ENOMEM; + goto done; + } + + if (debug_fd != -1) { + opt_logger = sss_logger_str[FILES_LOGGER]; + ret = set_debug_file_from_fd(debug_fd); + if (ret != EOK) { + opt_logger = sss_logger_str[STDERR_LOGGER]; + ERROR("set_debug_file_from_fd failed.\n"); + } + } + + sss_chain_id_set_format(DEBUG_CHAIN_ID_FMT_CID); + sss_chain_id_set((uint64_t)chain_id); + + DEBUG_INIT(debug_level, opt_logger); + + DEBUG(SSSDBG_TRACE_FUNC, "p11_child started.\n"); + + DEBUG(SSSDBG_TRACE_INTERNAL, "Running in [%s] mode.\n", op_mode_str(mode)); + + DEBUG(SSSDBG_TRACE_INTERNAL, + "Running with effective IDs: [%"SPRIuid"][%"SPRIgid"].\n", + geteuid(), getegid()); + + DEBUG(SSSDBG_TRACE_INTERNAL, + "Running with real IDs [%"SPRIuid"][%"SPRIgid"].\n", + getuid(), getgid()); + + main_ctx = talloc_new(NULL); + if (main_ctx == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new failed.\n"); + talloc_free(discard_const(debug_prg_name)); + ret = ENOMEM; + goto done; + } + talloc_steal(main_ctx, debug_prg_name); + + /* We do not require the label, but it is recommended */ + if (mode == OP_AUTH && (module_name == NULL || token_name == NULL + || key_id == NULL)) { + DEBUG(SSSDBG_FATAL_FAILURE, + "--module_name, --token_name and --key_id must be given for " + "authentication\n"); + ret = EINVAL; + goto done; + } + + ret = parse_cert_verify_opts(main_ctx, verify_opts, &cert_verify_opts); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Failed to parse verify option.\n"); + ret = EINVAL; + goto done; + } + + if (mode == OP_VERIFIY && !cert_verify_opts->do_verification) { + fprintf(stderr, + "Called verification with option 'no_verification', " + "it this intended?\n"); + } + + if (mode == OP_AUTH && pin_mode == PIN_STDIN) { + ret = p11c_recv_data(main_ctx, STDIN_FILENO, &pin); + if (ret != EOK) { + DEBUG(SSSDBG_FATAL_FAILURE, "Failed to read PIN.\n"); + ret = EINVAL; + goto done; + } + } + + ret = do_work(main_ctx, mode, ca_db, cert_verify_opts, wait_for_card, + cert_b64, pin, module_name, token_name, key_id, label, uri, + &multi); + +done: + fprintf(stdout, "%d\n%s", ret, multi ? multi : ""); + + talloc_free(main_ctx); + + if (ret == EOK) { + return EXIT_SUCCESS; + } else if (ret == ERR_CA_DB_NOT_FOUND) { + DEBUG(SSSDBG_CRIT_FAILURE, "p11_child failed - CA DB not found\n"); + return CA_DB_NOT_FOUND_EXIT_CODE; + } else { + DEBUG(SSSDBG_CRIT_FAILURE, "p11_child failed (%d)\n", ret); + return EXIT_FAILURE; + } +} diff --git a/src/p11_child/p11_child_common_utils.c b/src/p11_child/p11_child_common_utils.c new file mode 100644 index 0000000..b281852 --- /dev/null +++ b/src/p11_child/p11_child_common_utils.c @@ -0,0 +1,241 @@ +/* + SSSD + + Helper child to commmunicate with SmartCard -- common code + + Authors: + Sumit Bose <sbose@redhat.com> + + Copyright (C) 2019 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 <talloc.h> + +#include "util/util.h" +#include "p11_child/p11_child.h" + +static struct cert_verify_opts *init_cert_verify_opts(TALLOC_CTX *mem_ctx) +{ + struct cert_verify_opts *cert_verify_opts; + + cert_verify_opts = talloc_zero(mem_ctx, struct cert_verify_opts); + if (cert_verify_opts == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_new failed.\n"); + return NULL; + } + + cert_verify_opts->do_ocsp = true; + cert_verify_opts->do_verification = true; + cert_verify_opts->verification_partial_chain = false; + cert_verify_opts->ocsp_default_responder = NULL; + cert_verify_opts->ocsp_default_responder_signing_cert = NULL; + cert_verify_opts->crl_files = NULL; + cert_verify_opts->ocsp_dgst = CKM_SHA_1; + cert_verify_opts->soft_ocsp = false; + cert_verify_opts->soft_crl = false; + + return cert_verify_opts; +} + +#define OCSP_DEFAUL_RESPONDER "ocsp_default_responder=" +#define OCSP_DEFAUL_RESPONDER_LEN (sizeof(OCSP_DEFAUL_RESPONDER) - 1) + +#define OCSP_DEFAUL_RESPONDER_SIGNING_CERT \ + "ocsp_default_responder_signing_cert=" +#define OCSP_DEFAUL_RESPONDER_SIGNING_CERT_LEN \ + (sizeof(OCSP_DEFAUL_RESPONDER_SIGNING_CERT) - 1) +#define CRL_FILE "crl_file=" +#define CRL_FILE_LEN (sizeof(CRL_FILE) -1) + +#define OCSP_DGST "ocsp_dgst=" +#define OCSP_DGST_LEN (sizeof(OCSP_DGST) -1) + +static errno_t parse_crl_files(const char *filename, size_t c, + struct cert_verify_opts *_opts) +{ + int ret; + + if (_opts->num_files == 0) { + _opts->crl_files = talloc_array(_opts, char *, 1); + } else { + _opts->crl_files = talloc_realloc(_opts, _opts->crl_files, + char *, _opts->num_files + 1); + } + if (_opts->crl_files == NULL) { + ret = ENOMEM; + goto done; + } + + _opts->crl_files[_opts->num_files] = talloc_strdup(_opts, + filename); + if (_opts->crl_files[_opts->num_files] == NULL + || *_opts->crl_files[_opts->num_files] == '\0') { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to parse crl_file option [%s].\n", filename); + ret = EINVAL; + goto done; + } + + _opts->num_files++; + ret = EOK; + +done: + return ret; +} + +errno_t parse_cert_verify_opts(TALLOC_CTX *mem_ctx, const char *verify_opts, + struct cert_verify_opts **_cert_verify_opts) +{ + int ret; + TALLOC_CTX *tmp_ctx; + char **opts; + size_t c; + struct cert_verify_opts *cert_verify_opts; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_new failed.\n"); + return ENOMEM; + } + + cert_verify_opts = init_cert_verify_opts(tmp_ctx); + if (cert_verify_opts == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "init_cert_verify_opts failed.\n"); + ret = ENOMEM; + goto done; + } + + if (verify_opts == NULL) { + ret = EOK; + goto done; + } + + ret = split_on_separator(tmp_ctx, verify_opts, ',', true, true, &opts, + NULL); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "split_on_separator failed.\n"); + goto done; + } + + for (c = 0; opts[c] != NULL; c++) { + if (strcasecmp(opts[c], "no_ocsp") == 0) { + DEBUG(SSSDBG_TRACE_ALL, + "Found 'no_ocsp' option, disabling OCSP.\n"); + cert_verify_opts->do_ocsp = false; + } else if (strcasecmp(opts[c], "no_verification") == 0) { + DEBUG(SSSDBG_CRIT_FAILURE, + "Found 'no_verification' option, " + "disabling verification completely. " + "This should not be used in production.\n"); + sss_log(SSS_LOG_CRIT, + "Smart card certificate verification disabled completely. " + "This should not be used in production."); + cert_verify_opts->do_verification = false; + } else if (strcasecmp(opts[c], "partial_chain") == 0) { + DEBUG(SSSDBG_TRACE_ALL, + "Found 'partial_chain' option, verification will not fail if " + "a complete chain cannot be built to a self-signed " + "trust-anchor, provided it is possible to construct a chain " + "to a trusted certificate that might not be self-signed.\n"); + cert_verify_opts->verification_partial_chain = true; + } else if (strncasecmp(opts[c], OCSP_DEFAUL_RESPONDER, + OCSP_DEFAUL_RESPONDER_LEN) == 0) { + cert_verify_opts->ocsp_default_responder = + talloc_strdup(cert_verify_opts, + &opts[c][OCSP_DEFAUL_RESPONDER_LEN]); + if (cert_verify_opts->ocsp_default_responder == NULL + || *cert_verify_opts->ocsp_default_responder == '\0') { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to parse ocsp_default_responder option [%s].\n", + opts[c]); + ret = EINVAL; + goto done; + } + + DEBUG(SSSDBG_TRACE_ALL, "Using OCSP default responder [%s]\n", + cert_verify_opts->ocsp_default_responder); + } else if (strncasecmp(opts[c], + OCSP_DEFAUL_RESPONDER_SIGNING_CERT, + OCSP_DEFAUL_RESPONDER_SIGNING_CERT_LEN) == 0) { + cert_verify_opts->ocsp_default_responder_signing_cert = + talloc_strdup(cert_verify_opts, + &opts[c][OCSP_DEFAUL_RESPONDER_SIGNING_CERT_LEN]); + if (cert_verify_opts->ocsp_default_responder_signing_cert == NULL + || *cert_verify_opts->ocsp_default_responder_signing_cert + == '\0') { + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to parse ocsp_default_responder_signing_cert " + "option [%s].\n", opts[c]); + ret = EINVAL; + goto done; + } + + DEBUG(SSSDBG_TRACE_ALL, + "Using OCSP default responder signing cert nickname [%s]\n", + cert_verify_opts->ocsp_default_responder_signing_cert); + } else if (strncasecmp(opts[c], CRL_FILE, CRL_FILE_LEN) == 0) { + ret = parse_crl_files(&opts[c][CRL_FILE_LEN], c, cert_verify_opts); + if (ret != EOK) { + goto done; + } + } else if (strncasecmp(opts[c], OCSP_DGST, OCSP_DGST_LEN) == 0) { + if (strcmp("sha1", &opts[c][OCSP_DGST_LEN]) == 0) { + cert_verify_opts->ocsp_dgst = CKM_SHA_1; + DEBUG(SSSDBG_TRACE_ALL, "Using sha1 for OCSP.\n"); + } else if (strcmp("sha256", &opts[c][OCSP_DGST_LEN]) == 0) { + cert_verify_opts->ocsp_dgst = CKM_SHA256; + DEBUG(SSSDBG_TRACE_ALL, "Using sha256 for OCSP.\n"); + } else if (strcmp("sha384", &opts[c][OCSP_DGST_LEN]) == 0) { + cert_verify_opts->ocsp_dgst = CKM_SHA384; + DEBUG(SSSDBG_TRACE_ALL, "Using sha384 for OCSP.\n"); + } else if (strcmp("sha512", &opts[c][OCSP_DGST_LEN]) == 0) { + cert_verify_opts->ocsp_dgst = CKM_SHA512; + DEBUG(SSSDBG_TRACE_ALL, "Using sha512 for OCSP.\n"); + } else { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unsupported digest for OCSP [%s], " + "using default sha1.\n", &opts[c][OCSP_DGST_LEN]); + cert_verify_opts->ocsp_dgst = CKM_SHA_1; + } + } else if (strcasecmp(opts[c], "soft_ocsp") == 0) { + DEBUG(SSSDBG_TRACE_ALL, + "Found 'soft_ocsp' option, verification will not fail if " + "OCSP responder cannot be connected.\n"); + cert_verify_opts->soft_ocsp = true; + } else if (strcasecmp(opts[c], "soft_crl") == 0) { + DEBUG(SSSDBG_TRACE_ALL, + "Found 'soft_crl' option, verification will not fail if " + "CRL is expired.\n"); + cert_verify_opts->soft_crl = true; + } else { + DEBUG(SSSDBG_CRIT_FAILURE, + "Unsupported certificate verification option [%s], " \ + "skipping.\n", opts[c]); + } + } + + ret = EOK; + +done: + if (ret == EOK) { + *_cert_verify_opts = talloc_steal(mem_ctx, cert_verify_opts); + } + + talloc_free(tmp_ctx); + + return ret; +} diff --git a/src/p11_child/p11_child_openssl.c b/src/p11_child/p11_child_openssl.c new file mode 100644 index 0000000..45a4930 --- /dev/null +++ b/src/p11_child/p11_child_openssl.c @@ -0,0 +1,2140 @@ +/* + SSSD + + Helper child to commmunicate with SmartCard via OpenSSL + + Authors: + Sumit Bose <sbose@redhat.com> + + Copyright (C) 2018 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 <openssl/ssl.h> +#include <openssl/crypto.h> +#include <openssl/x509.h> +#include <openssl/err.h> +#include <openssl/rand.h> +#include <openssl/ocsp.h> +#include <p11-kit/p11-kit.h> +#include <p11-kit/uri.h> + +#include <popt.h> + +#include "util/util.h" +#include "util/crypto/sss_crypto.h" +#include "util/child_common.h" +#include "p11_child/p11_child.h" + +struct p11_ctx { + X509_STORE *x509_store; + const char *ca_db; + bool wait_for_card; + struct cert_verify_opts *cert_verify_opts; +}; + +static OCSP_RESPONSE *query_responder(BIO *cbio, const char *host, + const char *path, + OCSP_REQUEST *req, int req_timeout) +{ + int fd; + int rv; + OCSP_REQ_CTX *ctx = NULL; + OCSP_RESPONSE *rsp = NULL; + fd_set confds; + struct timeval tv; + + if (req_timeout != -1) { + BIO_set_nbio(cbio, 1); + } + + rv = BIO_do_connect(cbio); + + if ((rv <= 0) && ((req_timeout == -1) || !BIO_should_retry(cbio))) { + DEBUG(SSSDBG_OP_FAILURE, "Error connecting BIO\n"); + return NULL; + } + + if (BIO_get_fd(cbio, &fd) < 0) { + DEBUG(SSSDBG_OP_FAILURE, "Can't get connection fd\n"); + goto err; + } + + if (req_timeout != -1 && rv <= 0) { + FD_ZERO(&confds); + FD_SET(fd, &confds); + tv.tv_usec = 0; + tv.tv_sec = req_timeout; + rv = select(fd + 1, NULL, (void *)&confds, NULL, &tv); + if (rv == 0) { + DEBUG(SSSDBG_OP_FAILURE, "Timeout on connect\n"); + return NULL; + } + } + + ctx = OCSP_sendreq_new(cbio, path, NULL, -1); + if (ctx == NULL) { + return NULL; + } + + if (OCSP_REQ_CTX_add1_header(ctx, "Host", host) == 0) { + goto err; + } + + if (!OCSP_REQ_CTX_set1_req(ctx, req)) { + goto err; + } + + for (;;) { + rv = OCSP_sendreq_nbio(&rsp, ctx); + if (rv != -1) + break; + if (req_timeout == -1) + continue; + FD_ZERO(&confds); + FD_SET(fd, &confds); + tv.tv_usec = 0; + tv.tv_sec = req_timeout; + if (BIO_should_read(cbio)) { + rv = select(fd + 1, (void *)&confds, NULL, NULL, &tv); + } else if (BIO_should_write(cbio)) { + rv = select(fd + 1, NULL, (void *)&confds, NULL, &tv); + } else { + DEBUG(SSSDBG_OP_FAILURE, "Unexpected retry condition\n"); + goto err; + } + if (rv == 0) { + DEBUG(SSSDBG_OP_FAILURE, "Timeout on request\n"); + break; + } + if (rv == -1) { + DEBUG(SSSDBG_OP_FAILURE, "Select error\n"); + break; + } + + } + err: + OCSP_REQ_CTX_free(ctx); + + return rsp; +} + +#if OPENSSL_VERSION_NUMBER < 0x10100000L +#define TLS_client_method SSLv23_client_method +#define X509_STORE_get0_objects(store) (store->objs) +#define X509_OBJECT_get_type(object) (object->type) +#define X509_OBJECT_get0_X509(object) (object->data.x509) +#define EVP_MD_CTX_free EVP_MD_CTX_destroy +#define X509_CRL_get0_nextUpdate(object) (object->crl->nextUpdate) +#endif + +OCSP_RESPONSE *process_responder(OCSP_REQUEST *req, + const char *host, const char *path, + char *port, int use_ssl, + int req_timeout) +{ + BIO *cbio = NULL; + SSL_CTX *ctx = NULL; + OCSP_RESPONSE *resp = NULL; + + cbio = BIO_new_connect(host); + if (cbio == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Error creating connect BIO\n"); + goto end; + } + if (port != NULL) + BIO_set_conn_port(cbio, port); + if (use_ssl == 1) { + BIO *sbio; + ctx = SSL_CTX_new(TLS_client_method()); + if (ctx == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Error creating SSL context.\n"); + goto end; + } + SSL_CTX_set_mode(ctx, SSL_MODE_AUTO_RETRY); + sbio = BIO_new_ssl(ctx, 1); + cbio = BIO_push(sbio, cbio); + } + + resp = query_responder(cbio, host, path, req, req_timeout); + if (resp == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Error querying OCSP responder\n"); + } + + end: + BIO_free_all(cbio); + SSL_CTX_free(ctx); + return resp; +} + +static const EVP_MD *get_dgst(CK_MECHANISM_TYPE ocsp_dgst) +{ + const EVP_MD *dgst = NULL; + + switch (ocsp_dgst) { + case CKM_SHA_1: + dgst = EVP_sha1(); + break; + case CKM_SHA256: + dgst = EVP_sha256(); + break; + case CKM_SHA384: + dgst = EVP_sha384(); + break; + case CKM_SHA512: + dgst = EVP_sha512(); + break; + default: + DEBUG(SSSDBG_CRIT_FAILURE, "Unsupported digest type [%lu].\n", + ocsp_dgst); + dgst = NULL; + } + + return dgst; +} + +static char *get_issuer_subject_str(TALLOC_CTX *mem_ctx, X509 *cert) +{ + X509_NAME *issuer_name; + X509_NAME *subject_name; + char *tmp_str = NULL; + BIO *bio_mem = NULL; + int ret; + char *str = NULL; + long mem_len; + + issuer_name = X509_get_issuer_name(cert); + subject_name = X509_get_subject_name(cert); + + if (issuer_name == NULL || subject_name == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Missing issuer or subject.\n"); + return NULL; + } + + bio_mem = BIO_new(BIO_s_mem()); + if (bio_mem == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "BIO_new failed.\n"); + return NULL; + } + + ret = BIO_printf(bio_mem, "Issuer: ["); + if (ret == -1) { + DEBUG(SSSDBG_OP_FAILURE, "BIO_printf failed.\n"); + goto done; + } + + ret = X509_NAME_print_ex(bio_mem, issuer_name, 0, XN_FLAG_ONELINE); + if (ret == -1) { + DEBUG(SSSDBG_OP_FAILURE, "X509_NAME_print_ex failed.\n"); + goto done; + } + + ret = BIO_printf(bio_mem, "] Subject: ["); + if (ret == -1) { + DEBUG(SSSDBG_OP_FAILURE, "BIO_printf failed.\n"); + goto done; + } + + ret = X509_NAME_print_ex(bio_mem, subject_name, 0, XN_FLAG_ONELINE); + if (ret == -1) { + DEBUG(SSSDBG_OP_FAILURE, "X509_NAME_print_ex failed.\n"); + goto done; + } + + ret = BIO_printf(bio_mem, "]"); + if (ret == -1) { + DEBUG(SSSDBG_OP_FAILURE, "BIO_printf failed.\n"); + goto done; + } + + mem_len = BIO_get_mem_data(bio_mem, &tmp_str); + if (mem_len <= 0) { + DEBUG(SSSDBG_OP_FAILURE, "BIO_get_mem_data failed.\n"); + goto done; + } + + str = talloc_asprintf(mem_ctx, "%.*s", + mem_len < INT_MAX ? (int) mem_len : INT_MAX, tmp_str); + if (str == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_asprintf failed.\n"); + } + +done: + BIO_free_all(bio_mem); + + return str; +} + +static errno_t do_ocsp(struct p11_ctx *p11_ctx, X509 *cert) +{ + OCSP_REQUEST *ocsp_req = NULL; + OCSP_RESPONSE *ocsp_resp = NULL; + OCSP_BASICRESP *ocsp_basic = NULL; + OCSP_CERTID *cid = NULL; + STACK_OF(OPENSSL_STRING) *ocsp_urls = NULL; + char *url_str; + X509 *issuer = NULL; + int req_timeout = -1; + int status; + int ret = EIO; + int reason; + ASN1_GENERALIZEDTIME *revtime; + ASN1_GENERALIZEDTIME *thisupd; + ASN1_GENERALIZEDTIME *nextupd; + long grace_time = (5 * 60); /* Allow 5 minutes time difference when + * checking the validity of the OCSP response */ + char *host = NULL; + char *path = NULL; + char *port = NULL; + int use_ssl; + X509_NAME *issuer_name = NULL; + X509_OBJECT *x509_obj; + STACK_OF(X509_OBJECT) *store_objects; + const EVP_MD *ocsp_dgst = NULL; + char *tmp_str; + + ocsp_urls = X509_get1_ocsp(cert); + if (ocsp_urls == NULL + && p11_ctx->cert_verify_opts->ocsp_default_responder == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "No OCSP URL in certificate and no default responder defined, " + "skipping OCSP check.\n"); + return EOK; + } + + if (p11_ctx->cert_verify_opts->ocsp_default_responder != NULL) { + url_str = p11_ctx->cert_verify_opts->ocsp_default_responder; + } else { + if (sk_OPENSSL_STRING_num(ocsp_urls) > 1) { + DEBUG(SSSDBG_CONF_SETTINGS, + "Found more than 1 OCSP URLs, just using the first.\n"); + } + + url_str = sk_OPENSSL_STRING_value(ocsp_urls, 0); + } + + DEBUG(SSSDBG_TRACE_ALL, "Using OCSP URL [%s].\n", url_str); + + ret = OCSP_parse_url(url_str, &host, &port, &path, &use_ssl); + if (ret != 1) { + DEBUG(SSSDBG_OP_FAILURE, "OCSP_parse_url failed to parse [%s].\n", + url_str); + ret = EIO; + goto done; + } + + issuer_name = X509_get_issuer_name(cert); + if (issuer_name == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Certificate has no issuer, " + "cannot run OCSP check.\n"); + ret = EINVAL; + goto done; + } + + store_objects = X509_STORE_get0_objects(p11_ctx->x509_store); + if (store_objects == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, + "No objects found in certificate store, OCSP failed.\n"); + ret = EINVAL; + goto done; + } + + x509_obj = X509_OBJECT_retrieve_by_subject(store_objects, X509_LU_X509, + issuer_name); + if (x509_obj == NULL || X509_OBJECT_get_type(x509_obj) != X509_LU_X509) { + DEBUG(SSSDBG_CRIT_FAILURE, "Issuer not found.\n"); + ret = EIO; + goto done; + } + + issuer = X509_OBJECT_get0_X509(x509_obj); + + ocsp_req = OCSP_REQUEST_new(); + if (ocsp_req == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "OCSP_REQUEST_new failed.\n"); + ret = ENOMEM; + goto done; + } + + ocsp_dgst = get_dgst(p11_ctx->cert_verify_opts->ocsp_dgst); + if (ocsp_dgst == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Cannot determine configured digest function " + "for OCSP, using default sha1.\n"); + ocsp_dgst = EVP_sha1(); + } + cid = OCSP_cert_to_id(ocsp_dgst, cert, issuer); + if (cid == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "OCSP_cert_to_id failed.\n"); + ret = EIO; + goto done; + } + + if (OCSP_request_add0_id(ocsp_req, cid) == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "OCSP_request_add0_id failed.\n"); + ret = EIO; + goto done; + } + + OCSP_request_add1_nonce(ocsp_req, NULL, -1); + + ocsp_resp = process_responder(ocsp_req, host, path, port, use_ssl, + req_timeout); + if (ocsp_resp == NULL) { + if (p11_ctx->cert_verify_opts->soft_ocsp) { + tmp_str = get_issuer_subject_str(p11_ctx, cert); + DEBUG(SSSDBG_CRIT_FAILURE, + "Failed to get an OCSP response from [%s] for %s, but " + "'soft_ocsp' is set and OCSP check will be skipped.\n", + url_str, tmp_str == NULL ? " - not available -" : tmp_str); + sss_log(SSS_LOG_CRIT, + "Skipping OCSP check because 'soft_ocsp' is set and no " + "OCSP response is available from [%s] for %s.\n", url_str, + tmp_str == NULL ? " - not available -" : tmp_str); + talloc_free(tmp_str); + + ret = EOK; + } else { + DEBUG(SSSDBG_OP_FAILURE, "process_responder failed.\n"); + ret = EIO; + } + goto done; + } + + status = OCSP_response_status(ocsp_resp); + if (status != OCSP_RESPONSE_STATUS_SUCCESSFUL) { + DEBUG(SSSDBG_CRIT_FAILURE, "OCSP response error: [%d][%s].\n", + status, OCSP_response_status_str(status)); + ret = EIO; + goto done; + } + + ocsp_basic = OCSP_response_get1_basic(ocsp_resp); + if (ocsp_resp == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "OCSP_response_get1_basic failed.\n"); + ret = EIO; + goto done; + } + + switch (OCSP_check_nonce(ocsp_req, ocsp_basic)) { + case -1: + DEBUG(SSSDBG_CRIT_FAILURE, "No nonce in OCSP response. This might " + "indicate a replay attack or an OCSP responder which does not " + "support nonces. Accepting response.\n"); + break; + case 0: + DEBUG(SSSDBG_CRIT_FAILURE, "Nonce in OCSP response does not match the " + "one used in the request.\n"); + ret = EIO; + goto done; + break; + case 1: + DEBUG(SSSDBG_TRACE_ALL, "Nonce in OCSP response is the same as the one " + "used in the request.\n"); + break; + case 2: + case 3: + DEBUG(SSSDBG_CRIT_FAILURE, "Missing nonce in OCSP request, this should" + "never happen.\n"); + ret = EIO; + goto done; + break; + default: + DEBUG(SSSDBG_CRIT_FAILURE, "Unexpected result of OCSP_check_nonce.\n"); + } + + status = OCSP_basic_verify(ocsp_basic, NULL, p11_ctx->x509_store, 0); + if (status != 1) { + DEBUG(SSSDBG_CRIT_FAILURE, "OCSP_basic_verify() failed to verify OCSP " + "response.\n"); + ret = EIO; + goto done; + } + + ret = OCSP_resp_find_status(ocsp_basic, cid, &status, &reason, + &revtime, &thisupd, &nextupd); + if (ret != 1) { + DEBUG(SSSDBG_CRIT_FAILURE, "OCSP response does not contain status of " + "our certificate.\n"); + ret = EIO; + goto done; + } + + if (status != V_OCSP_CERTSTATUS_GOOD) { + DEBUG(SSSDBG_CRIT_FAILURE, "OCSP check failed with [%d][%s].\n", + status, OCSP_cert_status_str(status)); + if (status == V_OCSP_CERTSTATUS_REVOKED) { + DEBUG(SSSDBG_CRIT_FAILURE, "Certificate is revoked [%d][%s].\n", + reason, OCSP_crl_reason_str(reason)); + } + ret = EIO; + goto done; + } + + if (OCSP_check_validity(thisupd, nextupd, grace_time, -1) != 1) { + DEBUG(SSSDBG_CRIT_FAILURE, "OCSP response is not valid anymore.\n"); + ret = EIO; + goto done; + } + + DEBUG(SSSDBG_TRACE_ALL, "OCSP check was successful.\n"); + ret = EOK; + +done: + OCSP_BASICRESP_free(ocsp_basic); + OCSP_RESPONSE_free(ocsp_resp); + OCSP_REQUEST_free(ocsp_req); + + OPENSSL_free(host); + OPENSSL_free(port); + OPENSSL_free(path); + X509_email_free(ocsp_urls); + + return ret; +} + +static char *get_pkcs11_uri(TALLOC_CTX *mem_ctx, CK_INFO *module_info, + CK_SLOT_INFO *slot_info, CK_SLOT_ID slot_id, + CK_TOKEN_INFO *token_info, CK_ATTRIBUTE *label, + CK_ATTRIBUTE *id) +{ + P11KitUri *uri; + char *uri_str = NULL; + char *tmp_str = NULL; + int ret; + CK_OBJECT_CLASS cert_class = CKO_CERTIFICATE; + CK_ATTRIBUTE class_attr = {CKA_CLASS, &cert_class, sizeof(CK_OBJECT_CLASS)}; + + uri = p11_kit_uri_new(); + if (uri == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "p11_kit_uri_new failed.\n"); + return NULL; + } + + ret = p11_kit_uri_set_attribute(uri, label); + if (ret != P11_KIT_URI_OK) { + DEBUG(SSSDBG_OP_FAILURE, "p11_kit_uri_set_attribute failed.\n"); + goto done; + } + + ret = p11_kit_uri_set_attribute(uri, id); + if (ret != P11_KIT_URI_OK) { + DEBUG(SSSDBG_OP_FAILURE, "p11_kit_uri_set_attribute failed.\n"); + goto done; + } + + ret = p11_kit_uri_set_attribute(uri, &class_attr); + if (ret != P11_KIT_URI_OK) { + DEBUG(SSSDBG_OP_FAILURE, "p11_kit_uri_set_attribute failed.\n"); + goto done; + } + + + memcpy(p11_kit_uri_get_token_info(uri), token_info, sizeof(CK_TOKEN_INFO)); + + memcpy(p11_kit_uri_get_slot_info(uri), slot_info, sizeof(CK_SLOT_INFO)); + p11_kit_uri_set_slot_id(uri, slot_id); + + memcpy(p11_kit_uri_get_module_info(uri), module_info, sizeof(CK_INFO)); + + ret = p11_kit_uri_format(uri, P11_KIT_URI_FOR_ANY, &tmp_str); + if (ret != P11_KIT_URI_OK) { + DEBUG(SSSDBG_OP_FAILURE, "p11_kit_uri_format failed [%s].\n", + p11_kit_uri_message(ret)); + goto done; + } + + if (tmp_str != NULL) { + uri_str = talloc_strdup(mem_ctx, tmp_str); + free(tmp_str); + if (uri_str == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n"); + } + } + +done: + p11_kit_uri_free(uri); + + return uri_str; +} + +static int p11_ctx_destructor(struct p11_ctx *p11_ctx) +{ + X509_STORE_free(p11_ctx->x509_store); + + CRYPTO_cleanup_all_ex_data(); + + return 0; +} + +errno_t init_p11_ctx(TALLOC_CTX *mem_ctx, const char *ca_db, + bool wait_for_card, struct p11_ctx **p11_ctx) +{ + int ret; + struct p11_ctx *ctx; + + ctx = talloc_zero(mem_ctx, struct p11_ctx); + if (ctx == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_zero failed.\n"); + return ENOMEM; + } + + /* See https://wiki.openssl.org/index.php/Library_Initialization for + * details. */ +#if OPENSSL_VERSION_NUMBER >= 0x10100000L + ret = OPENSSL_init_ssl(0, NULL); +#else + ret = SSL_library_init(); +#endif + if (ret != 1) { + DEBUG(SSSDBG_FATAL_FAILURE, "Failed to initialize OpenSSL.\n"); + ret = EIO; + goto done; + } + + ctx->ca_db = ca_db; + ctx->wait_for_card = wait_for_card; + talloc_set_destructor(ctx, p11_ctx_destructor); + + *p11_ctx = ctx; + + ret = EOK; + +done: + if (ret != EOK) { + talloc_free(ctx); + } + + return ret; +} + +static int ensure_verify_param(X509_VERIFY_PARAM **verify_param_out) +{ + if (verify_param_out == NULL) { + return EINVAL; + } + + if (*verify_param_out != NULL) { + return EOK; + } + + *verify_param_out = X509_VERIFY_PARAM_new(); + if (*verify_param_out == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "X509_VERIFY_PARAM_new failed.\n"); + return ENOMEM; + } + + return EOK; +} + +errno_t init_verification(struct p11_ctx *p11_ctx, + struct cert_verify_opts *cert_verify_opts) +{ + int ret; + X509_STORE *store = NULL; + unsigned long err; + int file_index = 0; + X509_LOOKUP *lookup = NULL; + X509_VERIFY_PARAM *verify_param = NULL; + + store = X509_STORE_new(); + if (store == NULL) { + err = ERR_get_error(); + DEBUG(SSSDBG_OP_FAILURE, "X509_STORE_new failed [%lu][%s].\n", + err, ERR_error_string(err, NULL)); + return ENOMEM; + } + + lookup = X509_STORE_add_lookup(store, X509_LOOKUP_file()); + if (lookup == NULL) { + err = ERR_get_error(); + DEBUG(SSSDBG_OP_FAILURE, "X509_LOOKUP_file failed [%lu][%s].\n", + err, ERR_error_string(err, NULL)); + ret = EIO; + goto done; + } + + if (!X509_LOOKUP_load_file(lookup, p11_ctx->ca_db, X509_FILETYPE_PEM)) { + err = ERR_get_error(); + DEBUG(SSSDBG_OP_FAILURE, + "X509_LOOKUP_load_file [%s] failed [%lu][%s].\n", + p11_ctx->ca_db, err, ERR_error_string(err, NULL)); + + if (ERR_GET_LIB(err) == ERR_LIB_SYS && + ERR_GET_REASON(err) == ENOENT) { + ret = ERR_CA_DB_NOT_FOUND; + } else { + ret = EIO; + } + + goto done; + } + + if (cert_verify_opts->verification_partial_chain) { + if ((ret = ensure_verify_param (&verify_param)) != EOK) { + goto done; + } + X509_VERIFY_PARAM_set_flags(verify_param, X509_V_FLAG_PARTIAL_CHAIN); + } + + if (cert_verify_opts->crl_files != NULL) { + if ((ret = ensure_verify_param (&verify_param)) != EOK) { + goto done; + } + + X509_VERIFY_PARAM_set_flags(verify_param, (X509_V_FLAG_CRL_CHECK + | X509_V_FLAG_CRL_CHECK_ALL)); + + while (file_index < cert_verify_opts->num_files) { + ret = X509_load_crl_file(lookup, + cert_verify_opts->crl_files[file_index], + X509_FILETYPE_PEM); + if (ret == 0) { + err = ERR_get_error(); + DEBUG(SSSDBG_OP_FAILURE, + "X509_load_crl_file for [%s] failed [%lu][%s].\n", + cert_verify_opts->crl_files[file_index], + err, ERR_error_string(err, NULL)); + ret = EIO; + goto done; + } + + file_index++; + } + } + + if (verify_param != NULL) { + X509_STORE_set1_param(store, verify_param); + } + + p11_ctx->x509_store = store; + p11_ctx->cert_verify_opts = cert_verify_opts; + + ret = EOK; + +done: + if (ret != EOK) { + X509_STORE_free(store); + } + + if (verify_param != NULL) { + X509_VERIFY_PARAM_free(verify_param); + } + + return ret; +} + +static int b64_to_cert(const char *b64, X509 **cert) +{ + X509 *x509; + unsigned char *der = NULL; + const unsigned char *d; + size_t der_size; + + der = sss_base64_decode(NULL, b64, &der_size); + if (der == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sss_base64_decode failed.\n"); + return ENOMEM; + } + + d = (const unsigned char *) der; + x509 = d2i_X509(NULL, &d, (int) der_size); + talloc_free(der); + if (x509 == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "d2i_X509 failed.\n"); + return EINVAL; + } + + *cert = x509; + + return 0; +} + +bool do_verification(struct p11_ctx *p11_ctx, X509 *cert) +{ + bool res = false; + int ret; + X509_STORE_CTX *ctx = NULL; + unsigned long err; + char *tmp_str = NULL; + X509_VERIFY_PARAM *verify_param = NULL; + + ctx = X509_STORE_CTX_new(); + if (ctx == NULL) { + err = ERR_get_error(); + DEBUG(SSSDBG_OP_FAILURE, "X509_STORE_CTX_new failed [%lu][%s].\n", + err, ERR_error_string(err, NULL)); + return false; + } + + if (!X509_STORE_CTX_init(ctx, p11_ctx->x509_store, cert, NULL)) { + err = ERR_get_error(); + DEBUG(SSSDBG_OP_FAILURE, "X509_STORE_CTX_init failed [%lu][%s].\n", + err, ERR_error_string(err, NULL)); + goto done; + } + + ret = X509_verify_cert(ctx); + if (ret != 1) { + DEBUG(SSSDBG_OP_FAILURE, "X509_verify_cert failed [%d].\n", ret); + ret = X509_STORE_CTX_get_error(ctx); + if (ret == X509_V_ERR_CRL_HAS_EXPIRED + && p11_ctx->cert_verify_opts->soft_crl) { + tmp_str = get_issuer_subject_str(p11_ctx, cert); + DEBUG(SSSDBG_OP_FAILURE, "CRL is expired but 'soft_crl' is set, " + "ignoring CRL check for certificate %s.\n", + tmp_str == NULL ? " - not available - " + : tmp_str); + + /* We have to check again without the CRL check if the certificate + * is valid or not. The X509_STORE_CTX must be freshly initialized + * for another call to X509_verify_cert(), see e.g. + * man X509_STORE_CTX_init for details. */ + X509_STORE_CTX_cleanup(ctx); + if (!X509_STORE_CTX_init(ctx, p11_ctx->x509_store, cert, NULL)) { + err = ERR_get_error(); + DEBUG(SSSDBG_OP_FAILURE, + "X509_STORE_CTX_init failed [%lu][%s].\n", err, + ERR_error_string(err, NULL)); + goto done; + } + + verify_param = X509_STORE_CTX_get0_param(ctx); + if (verify_param == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "X509_VERIFY_PARAM_new failed.\n"); + goto done; + } + + X509_VERIFY_PARAM_clear_flags(verify_param, (X509_V_FLAG_CRL_CHECK + |X509_V_FLAG_CRL_CHECK_ALL)); + + ret = X509_verify_cert(ctx); + if (ret != 1) { + DEBUG(SSSDBG_OP_FAILURE, + "X509_verify_cert failed [%d].\n", ret); + ret = X509_STORE_CTX_get_error(ctx); + DEBUG(SSSDBG_OP_FAILURE, "X509_verify_cert failed [%d][%s].\n", + ret, + X509_verify_cert_error_string(ret)); + goto done; + } + + DEBUG(SSSDBG_TRACE_ALL, + "Certificate valid after ignoring expired CRL.\n"); + sss_log(SSS_LOG_CRIT, "Certificate %s is valid after ignoring " + "expired CRL because 'soft_crl' is set.\n", + tmp_str == NULL ? " - not available -" + :tmp_str); + + } else { + DEBUG(SSSDBG_OP_FAILURE, "X509_verify_cert failed [%d][%s].\n", + ret, X509_verify_cert_error_string(ret)); + goto done; + } + } + + if (p11_ctx->cert_verify_opts->do_ocsp) { + ret = do_ocsp(p11_ctx, cert); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "do_ocsp failed.\n"); + goto done; + } + } + + res = true; + +done: + talloc_free(tmp_str); + X509_STORE_CTX_free(ctx); + + return res; +} + +bool do_verification_b64(struct p11_ctx *p11_ctx, const char *cert_b64) +{ + int ret; + X509 *cert; + bool res; + + ret = b64_to_cert(cert_b64, &cert); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to convert certificate.\n"); + return false; + } + + res = do_verification(p11_ctx, cert); + X509_free(cert); + + return res; +} + +#define ATTR_ID 0 +#define ATTR_LABEL 1 +#define ATTR_CERT 2 + +struct cert_list { + struct cert_list *prev; + struct cert_list *next; + CK_ATTRIBUTE attributes[3]; + char *id; + char *label; + X509 *cert; + char *subject_dn; + char *cert_b64; + char *uri; + CK_KEY_TYPE key_type; + CK_OBJECT_HANDLE private_key; +}; + +static int free_x509_cert(struct cert_list *item) +{ + X509_free(item->cert); + return 0; +} + +static int read_certs(TALLOC_CTX *mem_ctx, CK_FUNCTION_LIST *module, + CK_SESSION_HANDLE session, struct p11_ctx *p11_ctx, + struct cert_list **cert_list) +{ + int ret; + size_t c; + CK_RV rv; + struct cert_list *list = NULL; + struct cert_list *item; + X509_NAME *tmp_name; + char *tmp_name_str; + + CK_OBJECT_CLASS cert_class = CKO_CERTIFICATE; + CK_CERTIFICATE_TYPE cert_type = CKC_X_509; + CK_ATTRIBUTE cert_find_template[] = { + {CKA_CLASS, &cert_class, sizeof(CK_OBJECT_CLASS)} , + {CKA_CERTIFICATE_TYPE, &cert_type, sizeof(CK_CERTIFICATE_TYPE)} + }; + + CK_ULONG obj_count; + CK_OBJECT_HANDLE obj; + + rv = module->C_FindObjectsInit(session, cert_find_template, 2); + if (rv != CKR_OK) { + DEBUG(SSSDBG_OP_FAILURE ,"C_FindObjectsInit failed [%lu][%s].\n", + rv, p11_kit_strerror(rv)); + return EIO; + } + + do { + rv = module->C_FindObjects(session, &obj, 1, &obj_count); + if (rv != CKR_OK) { + DEBUG(SSSDBG_OP_FAILURE ,"C_FindObject failed [%lu][%s].\n", + rv, p11_kit_strerror(rv)); + ret = EIO; + goto done; + } + + if (obj_count != 0) { + item = talloc_zero(mem_ctx, struct cert_list); + if (item == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_zero failed.\n"); + ret = ENOMEM; + goto done; + } + item->attributes[0].type = CKA_ID; + item->attributes[1].type = CKA_LABEL; + item->attributes[2].type = CKA_VALUE; + + rv = module->C_GetAttributeValue(session, obj, item->attributes, 3); + if (rv != CKR_OK) { + DEBUG(SSSDBG_OP_FAILURE, + "C_GetAttributeValue failed [%lu][%s].\n", + rv, p11_kit_strerror(rv)); + ret = EIO; + goto done; + } + + if (item->attributes[0].ulValueLen == -1 + || item->attributes[1].ulValueLen == -1 + || item->attributes[2].ulValueLen == -1) { + DEBUG(SSSDBG_OP_FAILURE, + "One of the needed attributes cannot be read.\n"); + ret = EIO; + goto done; + } + + item->attributes[0].pValue = talloc_size(item, + item->attributes[0].ulValueLen); + item->attributes[1].pValue = talloc_size(item, + item->attributes[1].ulValueLen); + item->attributes[2].pValue = talloc_size(item, + item->attributes[2].ulValueLen); + if (item->attributes[0].pValue == NULL + || item->attributes[1].pValue == NULL + || item->attributes[2].pValue == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_size failed.\n"); + ret = ENOMEM; + goto done; + } + + rv = module->C_GetAttributeValue(session, obj, item->attributes, 3); + if (rv != CKR_OK) { + DEBUG(SSSDBG_OP_FAILURE, + "C_GetAttributeValue failed [%lu][%s].\n", + rv, p11_kit_strerror(rv)); + ret = EIO; + goto done; + } + + item->label = talloc_strndup(item, item->attributes[1].pValue, + item->attributes[1].ulValueLen); + if (item->label == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strndup failed.\n"); + ret = ENOMEM; + goto done; + } + + item->id = talloc_zero_size(item, 2 * item->attributes[0].ulValueLen + 1); + if (item->id == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strndup failed.\n"); + ret = ENOMEM; + goto done; + } + + for (c = 0; c < item->attributes[0].ulValueLen; c++) { + ret = snprintf(item->id + 2*c, 3, "%02X", + ((uint8_t *)item->attributes[0].pValue)[c]); + if (ret != 2) { + DEBUG(SSSDBG_OP_FAILURE, "snprintf failed.\n"); + ret = EIO; + goto done; + } + } + + item->cert_b64 = sss_base64_encode(item, + item->attributes[2].pValue, + item->attributes[2].ulValueLen); + if (item->cert_b64 == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sss_base64_encode failed.\n"); + ret = ENOMEM; + goto done; + } + + /* It looks like d2i_X509 modifies the given binary data, so do + * not use item->attributes[2].pValue after this call. */ + item->cert = d2i_X509(NULL, + (const unsigned char **)&item->attributes[2].pValue, + item->attributes[2].ulValueLen); + if (item->cert == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "d2i_X509 failed.\n"); + ret = EINVAL; + goto done; + } + talloc_set_destructor(item, free_x509_cert); + + tmp_name = X509_get_subject_name(item->cert); + tmp_name_str = X509_NAME_oneline(tmp_name, NULL, 0); + + item->subject_dn = talloc_strdup(item, tmp_name_str); + OPENSSL_free(tmp_name_str); + if (item->subject_dn == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_strdup failed.\n"); + ret = ENOMEM; + goto done; + } + + DEBUG(SSSDBG_TRACE_ALL, "found cert[%s][%s]\n", + item->label, item->subject_dn); + + if (p11_ctx->x509_store == NULL + || do_verification(p11_ctx, item->cert)) { + DLIST_ADD(list, item); + } else { + DEBUG(SSSDBG_OP_FAILURE, + "Certificate [%s][%s] not valid, skipping.\n", + item->label, + item->subject_dn); + talloc_free(item); + } + } + } while (obj_count != 0); + + *cert_list = list; + + ret = EOK; + +done: + rv = module->C_FindObjectsFinal(session); + if (rv != CKR_OK) { + DEBUG(SSSDBG_OP_FAILURE ,"C_FindObject failed [%lu][%s].\n", + rv, p11_kit_strerror(rv)); + ret = EIO; + } + + return ret; +} + +/* Currently this funtion is only used the print the curve type in the debug + * messages. */ +static void get_ec_curve_type(CK_FUNCTION_LIST *module, + CK_SESSION_HANDLE session, + CK_OBJECT_HANDLE key_handle) +{ + CK_ATTRIBUTE attribute; + CK_RV rv; + EC_GROUP *ec_group; + const unsigned char *p; + int len; + char der_buf[128]; /* FIXME: any other size ?? */ + char oid_buf[128]; /* FIXME: any other size ?? */ + + attribute.type = CKA_ECDSA_PARAMS; + attribute.pValue = &der_buf; + attribute.ulValueLen = sizeof(der_buf); + + rv = module->C_GetAttributeValue(session, key_handle, &attribute, 1); + if (rv != CKR_OK) { + DEBUG(SSSDBG_OP_FAILURE, + "C_GetAttributeValue failed [%lu][%s].\n", + rv, p11_kit_strerror(rv)); + return; + } + + p = (const unsigned char *) attribute.pValue; + ec_group = d2i_ECPKParameters(NULL, &p, attribute.ulValueLen); + len = OBJ_obj2txt(oid_buf, sizeof(oid_buf), + OBJ_nid2obj(EC_GROUP_get_curve_name(ec_group)), 1); + DEBUG(SSSDBG_TRACE_ALL, "Curve name [%s][%s][%.*s].\n", + OBJ_nid2sn(EC_GROUP_get_curve_name(ec_group)), + OBJ_nid2ln(EC_GROUP_get_curve_name(ec_group)), + len, oid_buf); + + return; +} + +static CK_KEY_TYPE get_key_type(CK_FUNCTION_LIST *module, + CK_SESSION_HANDLE session, + CK_OBJECT_HANDLE key_handle) +{ + CK_ATTRIBUTE attribute; + CK_RV rv; + CK_KEY_TYPE type; + + attribute.type = CKA_KEY_TYPE; + attribute.pValue = &type; + attribute.ulValueLen = sizeof(CK_KEY_TYPE); + + rv = module->C_GetAttributeValue(session, key_handle, &attribute, 1); + if (rv != CKR_OK) { + DEBUG(SSSDBG_OP_FAILURE, + "C_GetAttributeValue failed [%lu][%s].\n", + rv, p11_kit_strerror(rv)); + return CK_UNAVAILABLE_INFORMATION; + } + + if (attribute.ulValueLen == -1) { + DEBUG(SSSDBG_OP_FAILURE, + "Key type attribute cannot be read.\n"); + return CK_UNAVAILABLE_INFORMATION; + } + + if (type == CKK_EC && DEBUG_IS_SET(SSSDBG_TRACE_ALL)) { + get_ec_curve_type(module, session, key_handle); + } + + return type; +} + +static int do_sha512(TALLOC_CTX *mem_ctx, CK_BYTE *in, size_t in_len, + bool add_info, CK_BYTE **_hash, size_t *_hash_len) +{ + EVP_MD_CTX *md_ctx = NULL; + int ret; + unsigned char md_value[EVP_MAX_MD_SIZE]; + unsigned int md_len; + CK_BYTE *out = NULL; + const CK_BYTE info[] = + { /* https://datatracker.ietf.org/doc/html/rfc3447#page-43 : + the DER encoding T of the DigestInfo value for SHA-512 */ + 0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, + 0x65, 0x03, 0x04, 0x02, 0x03, 0x05, 0x00, 0x04, 0x40 + }; + const unsigned int info_len = add_info ? sizeof(info) : 0; + + md_ctx = EVP_MD_CTX_create(); + if (md_ctx == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "EVP_MD_CTX_create failed.\n"); + return ENOMEM; + } + + ret = EVP_DigestInit(md_ctx, EVP_sha512()); + if (ret != 1) { + DEBUG(SSSDBG_OP_FAILURE, "EVP_DigestInit failed.\n"); + ret = EINVAL; + goto done; + } + + ret = EVP_DigestUpdate(md_ctx, in, in_len); + if (ret != 1) { + DEBUG(SSSDBG_OP_FAILURE, "EVP_DigestUpdate failed.\n"); + ret = EINVAL; + goto done; + } + + ret = EVP_DigestFinal_ex(md_ctx, md_value, &md_len); + if (ret != 1) { + DEBUG(SSSDBG_OP_FAILURE, "EVP_DigestFinal failed.\n"); + ret = EINVAL; + goto done; + } + + out = talloc_size(mem_ctx, info_len + md_len * sizeof(CK_BYTE)); + if (out == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_size failed.\n"); + ret = ENOMEM; + goto done; + } + + if (add_info) { + memcpy(out, info, info_len); + } + + memcpy(out + info_len, md_value, md_len); + + *_hash = out; + *_hash_len = info_len + md_len; + + ret = EOK; + +done: + EVP_MD_CTX_free(md_ctx); + return ret; +} + +/* A ECDSA signature consists of 2 integer values r and s. According to the + * "PKCS #11 Cryptographic Token Interface Current Mechanisms Specification": + * + * """ + * For the purposes of these mechanisms, an ECDSA signature is an octet string + * of even length which is at most two times nLen octets, where nLen is the + * length in octets of the base point order n. The signature octets correspond + * to the concatenation of the ECDSA values r and s, both represented as an + * octet string of equal length of at most nLen with the most significant byte + * first. If r and s have different octet length, the shorter of both must be + * padded with leading zero octets such that both have the same octet length. + * Loosely spoken, the first half of the signature is r and the second half is + * s. For signatures created by a token, the resulting signature is always of + * length 2nLen. + * """ + * + * Unfortunately OpenSSL expects the 2 integer values r and s DER encoded as + * specified in X9.62 "Public Key Cryptography For The Financial Services + * Industry: The Elliptic Curve Digital Signature Algorithm (ECDSA)": + * + * """ + * When a digital signature is identified by the OID ecdsa-with-SHA1 , the + * digital signature shall be ASN.1 encoded using the following syntax: + * ECDSA-Sig-Value ::= SEQUENCE { + * r INTEGER, + * s INTEGER + * } + * """ + * + * The following function translates from the PKCS#11 to the X9.62 format by + * manually creating the DER sequence after splitting the PKCS#11 signature. + * Since r and s are positive values we have to make sure that the leading + * bit is not set in the DER encoding by prepending a 0-byte if needed. + */ +static int rs_to_seq(TALLOC_CTX *mem_ctx, CK_BYTE *rs_sig, CK_ULONG rs_sig_len, + CK_BYTE **seq_sig, CK_ULONG *seq_sig_len) +{ + CK_BYTE *r; + size_t r_len; + CK_BYTE *s; + size_t s_len; + size_t r_add = 0; + size_t s_add = 0; + CK_BYTE *out; + size_t out_len; + + if (rs_sig_len % 2 != 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "Unexpected signature size [%lu].\n", + rs_sig_len); + return EINVAL; + } + + r_len = s_len = rs_sig_len / 2; + r = rs_sig; + s = rs_sig + r_len; + + /* Remove padding */ + while(r_len > 1 && *r == 0x00) { + r++; + r_len--; + } + while(s_len > 1 && *s == 0x00) { + s++; + s_len--; + } + + /* r and s are positive, check if the highest bit is set which would + * indicate a negative value. In this case a 0x00 must be added. */ + if ( *r & 0x80 ) { + r_add = 1; + } + if ( *s & 0x80 ) { + s_add = 1; + } + + out_len = r_len + r_add + s_len + s_add + 6; + out = talloc_size(mem_ctx, out_len * sizeof(CK_BYTE)); + if (out == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_size failed.\n"); + return ENOMEM; + } + + out[0] = 0x30; + out[1] = (CK_BYTE) (out_len - 2); + out[2] = 0x02; + out[3] = (CK_BYTE) (r_len + r_add); + if (r_add == 1) { + out[4] = 0x00; + } + memcpy(&out[4 + r_add], r, r_len); + out[4 + r_add + r_len] = 0x02; + out[5 + r_add + r_len] = (CK_BYTE) (s_len + s_add); + if (s_add == 1) { + out[6 + r_add + r_len] = 0x00; + } + memcpy(&out[6 + r_add + r_len + s_add], s, s_len); + + *seq_sig = out; + *seq_sig_len = out_len; + + return EOK; +} + +static CK_RV get_preferred_rsa_mechanism(TALLOC_CTX *mem_ctx, + CK_FUNCTION_LIST *module, + CK_SLOT_ID slot_id, + CK_MECHANISM_TYPE *preferred_mechanism, + const EVP_MD **preferred_evp_md) +{ + CK_ULONG count; + CK_MECHANISM_TYPE *mechanism_list = NULL; + CK_RV rv; + size_t c; + size_t m; + const struct prefs { + CK_MECHANISM_TYPE mech; + const char *mech_name; + const EVP_MD *evp_md; + const char *md_name; + } prefs[] = { + { CKM_SHA512_RSA_PKCS, "CKM_SHA512_RSA_PKCS", EVP_sha512(), "sha512" }, + { CKM_SHA384_RSA_PKCS, "CKM_SHA384_RSA_PKCS", EVP_sha384(), "sha384" }, + { CKM_SHA256_RSA_PKCS, "CKM_SHA256_RSA_PKCS", EVP_sha256(), "sha256" }, + { CKM_SHA224_RSA_PKCS, "CKM_SHA224_RSA_PKCS", EVP_sha224(), "sha224" }, + { CKM_RSA_PKCS, "CKM_RSA_PKCS", NULL, "-none-" }, + { CKM_SHA1_RSA_PKCS, "CKM_SHA1_RSA_PKCS", EVP_sha1(), "sha1" }, + { 0, NULL, NULL, NULL } + }; + + rv = module->C_GetMechanismList(slot_id, NULL, &count); + if (rv != CKR_OK) { + DEBUG(SSSDBG_MINOR_FAILURE, "C_GetMechanismList failed: [%lu][%s]\n", + rv, p11_kit_strerror(rv)); + return rv; + } + if (count == 0) { + DEBUG(SSSDBG_MINOR_FAILURE, "No mechanism found\n"); + return CKR_GENERAL_ERROR; + } + + mechanism_list = talloc_size(mem_ctx, count * sizeof(CK_MECHANISM_TYPE)); + if (mechanism_list == NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, "Failed to allocate memory\n"); + return CKR_GENERAL_ERROR; + } + + rv = module->C_GetMechanismList(slot_id, mechanism_list, &count); + if (rv != CKR_OK) { + DEBUG(SSSDBG_MINOR_FAILURE, "2nd C_GetMechanismList failed: [%lu][%s]\n", + rv, p11_kit_strerror(rv)); + talloc_free(mechanism_list); + return rv; + } + + for (m = 0; m < count; m++) { + DEBUG(SSSDBG_TRACE_ALL, "Found mechanism [%lu].\n", mechanism_list[m]); + } + for (c = 0; prefs[c].mech != 0; c++) { + for (m = 0; m < count; m++) { + if (prefs[c].mech == mechanism_list[m]) { + *preferred_mechanism = prefs[c].mech; + *preferred_evp_md = prefs[c].evp_md; + DEBUG(SSSDBG_FUNC_DATA, + "Using PKCS#11 mechanism [%lu][%s] with " + "message digest [%s].\n", + *preferred_mechanism, prefs[c].mech_name, + prefs[c].md_name); + talloc_free(mechanism_list); + return CKR_OK; + } + } + } + talloc_free(mechanism_list); + + DEBUG(SSSDBG_MINOR_FAILURE, "No match found\n"); + return CKR_GENERAL_ERROR; +} + +static int sign_data(CK_FUNCTION_LIST *module, CK_SESSION_HANDLE session, + CK_SLOT_ID slot_id, + struct cert_list *cert) +{ + CK_OBJECT_CLASS key_class = CKO_PRIVATE_KEY; + CK_BBOOL key_sign = CK_TRUE; + CK_ATTRIBUTE key_template[] = { + {CKA_CLASS, &key_class, sizeof(key_class)}, + {CKA_SIGN, &key_sign, sizeof(key_sign)}, + {CKA_ID, NULL, 0} + }; + CK_MECHANISM mechanism = { CK_UNAVAILABLE_INFORMATION, NULL, 0 }; + CK_MECHANISM_TYPE preferred_mechanism; + CK_OBJECT_HANDLE priv_key_object; + CK_ULONG object_count; + CK_BYTE random_value[128]; + CK_BYTE *signature = NULL; + CK_ULONG signature_size = 0; + CK_BYTE *seq_sig = NULL; + CK_ULONG seq_sig_size = 0; + CK_RV rv; + CK_RV rv_f; + EVP_PKEY *cert_pub_key = NULL; + EVP_MD_CTX *md_ctx = NULL; + int ret; + const EVP_MD *evp_md = NULL; + CK_BYTE *hash_val = NULL; + size_t hash_len = 0; + CK_BYTE *val_to_sign = NULL; + size_t val_to_sign_len = 0; + bool card_does_hash = false; + + key_template[2].pValue = cert->attributes[ATTR_ID].pValue; + key_template[2].ulValueLen = cert->attributes[ATTR_ID].ulValueLen; + + rv = module->C_FindObjectsInit(session, key_template, 3); + if (rv != CKR_OK) { + DEBUG(SSSDBG_OP_FAILURE ,"C_FindObjectsInit failed [%lu][%s].\n", + rv, p11_kit_strerror(rv)); + return EIO; + } + + rv = module->C_FindObjects(session, &priv_key_object, 1, &object_count); + rv_f = module->C_FindObjectsFinal(session); + if (rv != CKR_OK) { + DEBUG(SSSDBG_OP_FAILURE ,"C_FindObject failed [%lu][%s].\n", + rv, p11_kit_strerror(rv)); + return EIO; + } + if (rv_f != CKR_OK) { + DEBUG(SSSDBG_OP_FAILURE ,"C_FindObjectsFinal failed [%lu][%s].\n", + rv_f, p11_kit_strerror(rv_f)); + return EIO; + } + + if (object_count == 0) { + DEBUG(SSSDBG_CRIT_FAILURE, "No private key found.\n"); + return EINVAL; + } + + switch (get_key_type(module, session, priv_key_object)) { + case CKK_RSA: + rv = get_preferred_rsa_mechanism(cert, module, slot_id, + &preferred_mechanism, &evp_md); + if (rv != CKR_OK) { + DEBUG(SSSDBG_OP_FAILURE, "get_preferred_rsa_mechanism failed, " + "using default CKM_SHA1_RSA_PKCS.\n"); + preferred_mechanism = CKM_SHA1_RSA_PKCS; + evp_md = EVP_sha1(); + } + DEBUG(SSSDBG_TRACE_ALL, "Found RSA key using mechanism [%lu].\n", + preferred_mechanism); + mechanism.mechanism = preferred_mechanism; + card_does_hash = (evp_md != NULL); + break; + case CKK_EC: + DEBUG(SSSDBG_TRACE_ALL, "Found ECC key using CKM_ECDSA.\n"); + mechanism.mechanism = CKM_ECDSA; + card_does_hash = false; + break; + case CK_UNAVAILABLE_INFORMATION: + DEBUG(SSSDBG_CRIT_FAILURE, "get_key_type failed.\n"); + return EIO; + break; + default: + DEBUG(SSSDBG_CRIT_FAILURE, "Unsupported key type.\n"); + return EIO; + } + + rv = module->C_SignInit(session, &mechanism, priv_key_object); + if (rv != CKR_OK) { + DEBUG(SSSDBG_OP_FAILURE, "C_SignInit failed [%lu][%s].\n", + rv, p11_kit_strerror(rv)); + return EIO; + } + + ret = sss_generate_csprng_buffer((uint8_t *)random_value, + sizeof(random_value)); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sss_generate_csprng_buffer failed.\n"); + return EINVAL; + } + + if (card_does_hash) { + val_to_sign = random_value; + val_to_sign_len = sizeof(random_value); + } else { + evp_md = EVP_sha512(); + ret = do_sha512(cert, random_value, sizeof(random_value), + (mechanism.mechanism == CKM_RSA_PKCS), /* add_info */ + &hash_val, &hash_len); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "do_hash failed.\n"); + return ret; + } + + val_to_sign = hash_val; + val_to_sign_len = hash_len; + } + + rv = module->C_Sign(session, val_to_sign, val_to_sign_len, NULL, + &signature_size); + if (rv != CKR_OK || signature_size == 0) { + DEBUG(SSSDBG_OP_FAILURE, "C_Sign failed [%lu][%s].\n", + rv, p11_kit_strerror(rv)); + return EIO; + } + + signature = talloc_size(cert, signature_size); + if (signature == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_size failed.\n"); + return ENOMEM; + } + + rv = module->C_Sign(session, val_to_sign, val_to_sign_len, signature, + &signature_size); + if (rv != CKR_OK) { + DEBUG(SSSDBG_OP_FAILURE, "C_Sign failed [%lu][%s].\n", + rv, p11_kit_strerror(rv)); + return EIO; + } + + cert_pub_key = X509_get_pubkey(cert->cert); + if (cert_pub_key == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "X509_get_pubkey failed.\n"); + ret = EIO; + goto done; + } + + md_ctx = EVP_MD_CTX_create(); + if (md_ctx == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "EVP_MD_CTX_create failed.\n"); + ret = ENOMEM; + goto done; + } + ret = EVP_VerifyInit(md_ctx, evp_md); + if (ret != 1) { + DEBUG(SSSDBG_OP_FAILURE, "EVP_VerifyInit failed.\n"); + ret = EINVAL; + goto done; + } + + ret = EVP_VerifyUpdate(md_ctx, random_value, sizeof(random_value)); + if (ret != 1) { + DEBUG(SSSDBG_OP_FAILURE, "EVP_VerifyUpdate failed.\n"); + ret = EINVAL; + goto done; + } + + if (mechanism.mechanism == CKM_ECDSA) { + ret = rs_to_seq(signature, signature, signature_size, + &seq_sig, &seq_sig_size); + if (ret != EOK) { + DEBUG(SSSDBG_CRIT_FAILURE, "rs_to_seq failed.\n"); + goto done; + } + + ret = EVP_VerifyFinal(md_ctx, seq_sig, seq_sig_size, cert_pub_key); + if (ret != 1) { + DEBUG(SSSDBG_OP_FAILURE, "EVP_VerifyFinal failed.\n"); + ret = EINVAL; + goto done; + } + } else { + ret = EVP_VerifyFinal(md_ctx, signature, signature_size, cert_pub_key); + if (ret != 1) { + DEBUG(SSSDBG_OP_FAILURE, "EVP_VerifyFinal failed: '%s'\n", + ERR_reason_error_string(ERR_peek_last_error())); + ret = EINVAL; + goto done; + } + } + + ret = EOK; + +done: + EVP_MD_CTX_destroy(md_ctx); + talloc_free(hash_val); + talloc_free(signature); + EVP_PKEY_free(cert_pub_key); + + return ret; +} + +static errno_t wait_for_card(CK_FUNCTION_LIST *module, CK_SLOT_ID *slot_id, + CK_SLOT_INFO *info, CK_TOKEN_INFO *token_info, + P11KitUri *uri) +{ + CK_SLOT_ID uri_slot_id = -1; + CK_FLAGS wait_flags = 0; + CK_RV rv; + + if (uri != NULL) { + uri_slot_id = p11_kit_uri_get_slot_id(uri); + } + + do { + rv = module->C_WaitForSlotEvent(wait_flags, slot_id, NULL); + if (rv == CKR_FUNCTION_NOT_SUPPORTED + && !(wait_flags & CKF_DONT_BLOCK)) { + wait_flags |= CKF_DONT_BLOCK; + continue; + } else if (rv == CKR_NO_EVENT) { + /* Poor man's wait */ + sleep(PKCS11_SLOT_EVENT_WAIT_TIME); + continue; + } else if (rv != CKR_OK) { + DEBUG(SSSDBG_OP_FAILURE, + "C_WaitForSlotEvent failed [%lu][%s].\n", + rv, p11_kit_strerror(rv)); + return EIO; + } + + rv = module->C_GetSlotInfo(*slot_id, info); + if (rv != CKR_OK) { + DEBUG(SSSDBG_OP_FAILURE, "C_GetSlotInfo failed [%lu][%s].\n", + rv, p11_kit_strerror(rv)); + return EIO; + } + DEBUG(SSSDBG_TRACE_ALL, + "Description [%.*s] Manufacturer [%.*s] flags [%lu] " + "removable [%s] token present [%s].\n", + (int) p11_kit_space_strlen(info->slotDescription, + sizeof(info->slotDescription)), + info->slotDescription, + (int) p11_kit_space_strlen(info->manufacturerID, + sizeof(info->manufacturerID)), + info->manufacturerID, info->flags, + (info->flags & CKF_REMOVABLE_DEVICE) ? "true": "false", + (info->flags & CKF_TOKEN_PRESENT) ? "true": "false"); + + /* Check if really a token is present */ + if (!(info->flags & CKF_REMOVABLE_DEVICE) + || !(info->flags & CKF_TOKEN_PRESENT)) { + continue; + } + + if (uri != NULL) { + if (uri_slot_id != (CK_SLOT_ID)-1 + && uri_slot_id != *slot_id) { + DEBUG(SSSDBG_TRACE_ALL, + "Slot ID does not match URI; skipping.\n"); + continue; + } + + if (p11_kit_uri_match_slot_info(uri, info) != 1) { + DEBUG(SSSDBG_TRACE_ALL, + "Slot info does not match URI; skipping.\n"); + continue; + } + } + + rv = module->C_GetTokenInfo(*slot_id, token_info); + if (rv != CKR_OK) { + DEBUG(SSSDBG_OP_FAILURE, "C_GetTokenInfo failed [%lu][%s].\n", + rv, p11_kit_strerror(rv)); + return EIO; + } + + if (!(token_info->flags & CKF_TOKEN_INITIALIZED)) { + DEBUG(SSSDBG_TRACE_ALL, "Token is not initialized; skipping.\n"); + continue; + } + + DEBUG(SSSDBG_TRACE_ALL, "Token label [%.*s].\n", + (int) p11_kit_space_strlen(token_info->label, + sizeof(token_info->label)), + token_info->label); + + if (uri != NULL) { + if (p11_kit_uri_match_token_info(uri, token_info) != 1) { + DEBUG(SSSDBG_TRACE_ALL, + "Token info does not match URI; skipping.\n"); + continue; + } + } + + break; + } while (true); + + return EOK; +} + +#define MAX_SLOTS 64 + +errno_t do_card(TALLOC_CTX *mem_ctx, struct p11_ctx *p11_ctx, + enum op_mode mode, const char *pin, + const char *module_name_in, const char *token_name_in, + const char *key_id_in, const char *label_in, + const char *uri_str, char **_multi) +{ + int ret; + size_t c; + size_t s = 0; + CK_FUNCTION_LIST **modules = NULL; + CK_FUNCTION_LIST *module = NULL; + char *mod_name; + char *mod_file_name; + CK_ULONG num_slots; + CK_SLOT_ID slots[MAX_SLOTS]; + CK_SLOT_ID slot_id = -1; + CK_SLOT_ID uri_slot_id = -1; + CK_SLOT_INFO info; + CK_TOKEN_INFO token_info; + CK_INFO module_info; + CK_RV rv; + size_t module_id; + char *module_file_name = NULL; + char *slot_name = NULL; + char *token_name = NULL; + CK_SESSION_HANDLE session = 0; + struct cert_list *cert_list = NULL; + struct cert_list *item = NULL; + struct cert_list *next_item = NULL; + char *multi = NULL; + bool pkcs11_session = false; + bool pkcs11_login = false; + P11KitUri *uri = NULL; + + if (uri_str != NULL) { + uri = p11_kit_uri_new(); + if (uri == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "p11_kit_uri_new failed.\n"); + return ENOMEM; + } + + ret = p11_kit_uri_parse(uri_str, P11_KIT_URI_FOR_ANY, uri); + if (ret != P11_KIT_URI_OK) { + DEBUG(SSSDBG_OP_FAILURE, "p11_kit_uri_parse failed [%d][%s].\n", + ret, p11_kit_uri_message(ret)); + ret = EINVAL; + goto done; + } + + uri_slot_id = p11_kit_uri_get_slot_id(uri); + + DEBUG(SSSDBG_TRACE_ALL, "URI: %s\n", uri_str); + } + + + /* Maybe use P11_KIT_MODULE_TRUSTED ? */ + modules = p11_kit_modules_load_and_initialize(0); + if (modules == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "p11_kit_modules_load_and_initialize failed.\n"); + ret = EIO; + goto done; + } + + for (;;) { + DEBUG(SSSDBG_TRACE_ALL, "Module List:\n"); + for (c = 0; modules[c] != NULL; c++) { + mod_name = p11_kit_module_get_name(modules[c]); + if (mod_name == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "p11_kit_module_get_name failed.\n"); + ret = ENOMEM; + goto done; + } + + mod_file_name = p11_kit_module_get_filename(modules[c]); + if (mod_file_name == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "p11_kit_module_get_filename failed.\n"); + ret = ENOMEM; + goto done; + } + + DEBUG(SSSDBG_TRACE_ALL, "common name: [%s].\n", mod_name); + DEBUG(SSSDBG_TRACE_ALL, "dll name: [%s].\n", mod_file_name); + + free(mod_name); + free(mod_file_name); + + rv = modules[c]->C_GetInfo(&module_info); + if (rv != CKR_OK) { + DEBUG(SSSDBG_OP_FAILURE, "C_GetInfo failed [%lu][%s].\n", + rv, p11_kit_strerror(rv)); + ret = EIO; + goto done; + } + + /* Skip modules which do not match the PKCS#11 URI */ + if (uri != NULL) { + if (p11_kit_uri_match_module_info(uri, &module_info) != 1) { + DEBUG(SSSDBG_TRACE_ALL, + "Module info does not match URI; skipping.\n"); + continue; + } + } + + /* After obtaining the module's slot list (previously in this loop), + * this call is needed to let any changes in slots take effect. */ + rv = modules[c]->C_GetSlotList(CK_FALSE, NULL, &num_slots); + if (rv != CKR_OK) { + DEBUG(SSSDBG_OP_FAILURE, "C_GetSlotList failed [%lu][%s].\n", + rv, p11_kit_strerror(rv)); + ret = EIO; + goto done; + } + + num_slots = MAX_SLOTS; + rv = modules[c]->C_GetSlotList(CK_FALSE, slots, &num_slots); + if (rv != CKR_OK) { + DEBUG(SSSDBG_OP_FAILURE, "C_GetSlotList failed [%lu][%s].\n", + rv, p11_kit_strerror(rv)); + ret = EIO; + goto done; + } + + for (s = 0; s < num_slots; s++) { + rv = modules[c]->C_GetSlotInfo(slots[s], &info); + if (rv != CKR_OK) { + DEBUG(SSSDBG_OP_FAILURE, + "C_GetSlotInfo failed [%lu][%s].\n", + rv, p11_kit_strerror(rv)); + ret = EIO; + goto done; + } + DEBUG(SSSDBG_TRACE_ALL, + "Description [%.*s] Manufacturer [%.*s] flags [%lu] " + "removable [%s] token present [%s].\n", + (int) p11_kit_space_strlen(info.slotDescription, + sizeof(info.slotDescription)), + info.slotDescription, + (int) p11_kit_space_strlen(info.manufacturerID, + sizeof(info.manufacturerID)), + info.manufacturerID, info.flags, + (info.flags & CKF_REMOVABLE_DEVICE) ? "true": "false", + (info.flags & CKF_TOKEN_PRESENT) ? "true": "false"); + + if (!(info.flags & CKF_REMOVABLE_DEVICE)) { + continue; + } + + module = modules[c]; + + if (!(info.flags & CKF_TOKEN_PRESENT)) { + continue; + } + + /* Skip slots which do not match the PKCS#11 URI */ + if (uri != NULL) { + if (uri_slot_id != (CK_SLOT_ID)-1 + && uri_slot_id != slots[s]) { + DEBUG(SSSDBG_TRACE_ALL, + "Slot ID does not match URI; skipping.\n"); + continue; + } + + if (p11_kit_uri_match_slot_info(uri, &info) != 1) { + DEBUG(SSSDBG_TRACE_ALL, + "Slot info does not match URI; skipping.\n"); + continue; + } + } + + rv = modules[c]->C_GetTokenInfo(slots[s], &token_info); + if (rv != CKR_OK) { + DEBUG(SSSDBG_OP_FAILURE, + "C_GetTokenInfo failed [%lu][%s].\n", + rv, p11_kit_strerror(rv)); + ret = EIO; + goto done; + } + + if (!(token_info.flags & CKF_TOKEN_INITIALIZED)) { + DEBUG(SSSDBG_TRACE_ALL, + "Token is not initialized; skipping.\n"); + continue; + } + + DEBUG(SSSDBG_TRACE_ALL, "Token label [%.*s].\n", + (int) p11_kit_space_strlen(token_info.label, + sizeof(token_info.label)), + token_info.label); + + if (uri != NULL) { + if (p11_kit_uri_match_token_info(uri, &token_info) != 1) { + DEBUG(SSSDBG_TRACE_ALL, + "Token info does not match URI; skipping.\n"); + continue; + } + + } + + slot_id = slots[s]; + break; + } + if (slot_id != (CK_SLOT_ID)-1) { + break; + } + } + + /* When e.g. using Yubikeys the slot isn't present until the device is + * inserted, so we should wait for a slot as well. */ + if (p11_ctx->wait_for_card && module == NULL) { + sleep(PKCS11_SLOT_EVENT_WAIT_TIME); + } else { + break; + } + } + + if (module == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "No removable slots found.\n"); + ret = EIO; + goto done; + } + + if (slot_id == (CK_SLOT_ID)-1) { + DEBUG(SSSDBG_TRACE_ALL, "Token not present.\n"); + if (p11_ctx->wait_for_card) { + /* After obtaining the module's slot list (in the loop above), this + * call is needed to let any changes in slots take effect. */ + rv = module->C_GetSlotList(CK_FALSE, NULL, &num_slots); + if (rv != CKR_OK) { + DEBUG(SSSDBG_OP_FAILURE, "C_GetSlotList failed [%lu][%s].\n", + rv, p11_kit_strerror(rv)); + ret = EIO; + goto done; + } + + ret = wait_for_card(module, &slot_id, &info, &token_info, uri); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "wait_for_card failed.\n"); + goto done; + } + } else { + ret = EIO; + goto done; + } + } + + module_id = c; + slot_name = p11_kit_space_strdup(info.slotDescription, + sizeof(info.slotDescription)); + if (slot_name == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "p11_kit_space_strdup failed.\n"); + ret = ENOMEM; + goto done; + } + + token_name = p11_kit_space_strdup(token_info.label, + sizeof(token_info.label)); + if (token_name == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "p11_kit_space_strdup failed.\n"); + ret = ENOMEM; + goto done; + } + + module_file_name = p11_kit_module_get_filename(module); + if (module_file_name == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "p11_kit_module_get_filename failed.\n"); + ret = ENOMEM; + goto done; + } + + DEBUG(SSSDBG_TRACE_ALL, "Found [%s] in slot [%s][%d] of module [%d][%s].\n", + token_name, slot_name, (int) slot_id, (int) module_id, + module_file_name); + + rv = module->C_OpenSession(slot_id, CKF_SERIAL_SESSION, NULL, NULL, + &session); + if (rv != CKR_OK) { + DEBUG(SSSDBG_OP_FAILURE, "C_OpenSession failed [%lu][%s].\n", + rv, p11_kit_strerror(rv)); + ret = EIO; + goto done; + } + pkcs11_session = true; + + /* login: do we need to check for Login Required? */ + if (mode == OP_AUTH) { + DEBUG(SSSDBG_TRACE_ALL, "Login required.\n"); + if (pin != NULL) { + rv = module->C_Login(session, CKU_USER, discard_const(pin), + strlen(pin)); + if (rv == CKR_PIN_LOCKED) { + DEBUG(SSSDBG_OP_FAILURE, "C_Login failed: PIN locked\n"); + ret = ERR_P11_PIN_LOCKED; + goto done; + } + else if (rv != CKR_OK) { + DEBUG(SSSDBG_OP_FAILURE, "C_Login failed [%lu][%s].\n", + rv, p11_kit_strerror(rv)); + ret = EIO; + goto done; + } + pkcs11_login = true; + } else { + DEBUG(SSSDBG_CRIT_FAILURE, + "Login required but no PIN available, continue.\n"); + } + } else { + DEBUG(SSSDBG_TRACE_ALL, "Login NOT required.\n"); + } + + ret = read_certs(mem_ctx, module, session, p11_ctx, &cert_list); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "read_certs failed.\n"); + goto done; + } + + DLIST_FOR_EACH_SAFE(item, next_item, cert_list) { + /* Check if we found the certificates we needed for authentication or + * the requested ones for pre-auth. For authentication all attributes + * except the label must be given and match. The label is optional for + * authentication but if given it must match as well. For pre-auth + * only the given ones must match. */ + DEBUG(SSSDBG_TRACE_ALL, "%s %s %s %s %s %s %s.\n", + module_name_in, module_file_name, token_name_in, token_name, + key_id_in, label_in == NULL ? "- no label given-" : label_in, + item->id); + + if ((mode == OP_AUTH + && module_name_in != NULL + && token_name_in != NULL + && key_id_in != NULL + && item->id != NULL + && strcmp(key_id_in, item->id) == 0 + && (label_in == NULL + || (label_in != NULL && item->label != NULL + && strcmp(label_in, item->label) == 0)) + && strcmp(token_name_in, token_name) == 0 + && strcmp(module_name_in, module_file_name) == 0) + || (mode == OP_PREAUTH + && (module_name_in == NULL + || (module_name_in != NULL + && strcmp(module_name_in, module_file_name) == 0)) + && (token_name_in == NULL + || (token_name_in != NULL + && strcmp(token_name_in, token_name) == 0)) + && (key_id_in == NULL + || (key_id_in != NULL && item->id != NULL + && strcmp(key_id_in, item->id) == 0)))) { + + item->uri = get_pkcs11_uri(mem_ctx, &module_info, &info, slot_id, + &token_info, + &item->attributes[1] /* label */, + &item->attributes[0] /* id */); + DEBUG(SSSDBG_TRACE_ALL, "uri: %s.\n", item->uri); + + } else { + DLIST_REMOVE(cert_list, item); + talloc_free(item); + } + } + + /* TODO: check module_name_in, token_name_in, key_id_in */ + + if (cert_list == NULL) { + DEBUG(SSSDBG_TRACE_ALL, "No certificate found.\n"); + *_multi = NULL; + ret = EOK; + goto done; + } + + if (mode == OP_AUTH) { + if (cert_list->next != NULL || cert_list->prev != NULL) { + DEBUG(SSSDBG_FATAL_FAILURE, + "More than one certificate found for authentication, " + "aborting!\n"); + ret = EINVAL; + goto done; + } + + ret = sign_data(module, session, slot_id, cert_list); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, "sign_data failed.\n"); + ret = EACCES; + goto done; + } + + DEBUG(SSSDBG_TRACE_ALL, + "Certificate verified and validated.\n"); + } + + multi = talloc_strdup(mem_ctx, ""); + if (multi == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "Failed to create output string.\n"); + ret = ENOMEM; + goto done; + } + + DLIST_FOR_EACH(item, cert_list) { + DEBUG(SSSDBG_TRACE_ALL, "Found certificate has key id [%s].\n", + item->id); + + multi = talloc_asprintf_append(multi, "%s\n%s\n%s\n%s\n%s\n", + token_name, module_file_name, item->id, + item->label, item->cert_b64); + } + + *_multi = multi; + + ret = EOK; +done: + if (module != NULL) { + if (pkcs11_login) { + rv = module->C_Logout(session); + if (rv != CKR_OK) { + DEBUG(SSSDBG_OP_FAILURE, "C_Logout failed [%lu][%s].\n", + rv, p11_kit_strerror(rv)); + } + } + if (pkcs11_session) { + rv = module->C_CloseSession(session); + if (rv != CKR_OK) { + DEBUG(SSSDBG_OP_FAILURE, "C_CloseSession failed [%lu][%s].\n", + rv, p11_kit_strerror(rv)); + } + } + } + free(slot_name); + free(token_name); + free(module_file_name); + p11_kit_modules_finalize_and_release(modules); + p11_kit_uri_free(uri); + + return ret; +} |