diff options
Diffstat (limited to 'src/passkey_child')
-rw-r--r-- | src/passkey_child/passkey_child.c | 106 | ||||
-rw-r--r-- | src/passkey_child/passkey_child.h | 557 | ||||
-rw-r--r-- | src/passkey_child/passkey_child_assert.c | 448 | ||||
-rw-r--r-- | src/passkey_child/passkey_child_common.c | 885 | ||||
-rw-r--r-- | src/passkey_child/passkey_child_credentials.c | 681 | ||||
-rw-r--r-- | src/passkey_child/passkey_child_devices.c | 246 |
6 files changed, 2923 insertions, 0 deletions
diff --git a/src/passkey_child/passkey_child.c b/src/passkey_child/passkey_child.c new file mode 100644 index 0000000..3209966 --- /dev/null +++ b/src/passkey_child/passkey_child.c @@ -0,0 +1,106 @@ +/* + SSSD + + Helper child to commmunicate with passkey devices + + Authors: + Iker Pedrosa <ipedrosa@redhat.com> + + Copyright (C) 2022 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 <fido.h> +#include <fido/param.h> + +#include "util/debug.h" +#include "util/util.h" + +#include "passkey_child.h" + +int main(int argc, const char *argv[]) +{ + TALLOC_CTX *main_ctx = NULL; + struct passkey_data data; + int init_flags = 0; + errno_t ret = EOK; + + main_ctx = talloc_new(NULL); + if (main_ctx == NULL) { + ERROR("talloc_new() failed.\n"); + talloc_free(discard_const(debug_prg_name)); + ret = ENOMEM; + goto done; + } + + ret = parse_arguments(main_ctx, argc, argv, &data); + if (ret != EOK) { + ERROR("Error parsing argument(s).\n"); + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "passkey_child started.\n"); + talloc_steal(main_ctx, debug_prg_name); + + ret = check_arguments(&data); + if (ret != EOK) { + ERROR("Invalid argument(s).\n"); + goto done; + } + + init_flags = (int)data.debug_libfido2 | FIDO_DISABLE_U2F_FALLBACK; + fido_init(init_flags); + + if (data.action == ACTION_REGISTER) { + ret = register_key(&data); + if (ret != EOK) { + ERROR("Error registering key.\n"); + goto done; + } + } else if (data.action == ACTION_AUTHENTICATE) { + ret = authenticate(&data); + if (ret == EOK) { + PRINT("Authentication success.\n"); + goto done; + } else { + ERROR("Authentication error.\n"); + goto done; + } + } else if (data.action == ACTION_GET_ASSERT) { + ret = get_assert_data(&data); + if (ret != EOK) { + ERROR("Error getting assertion data.\n"); + goto done; + } + } else if (data.action == ACTION_VERIFY_ASSERT) { + ret = verify_assert_data(&data); + if (ret == EOK) { + PRINT("Verification success.\n"); + goto done; + } else { + ERROR("Verification error.\n"); + goto done; + } + } + +done: + talloc_free(main_ctx); + + if (ret != EOK) { + return EXIT_FAILURE; + } else { + return EXIT_SUCCESS; + } +} diff --git a/src/passkey_child/passkey_child.h b/src/passkey_child/passkey_child.h new file mode 100644 index 0000000..185e7e6 --- /dev/null +++ b/src/passkey_child/passkey_child.h @@ -0,0 +1,557 @@ +/* + SSSD + + Helper child to commmunicate with passkey devices + + Authors: + Iker Pedrosa <ipedrosa@redhat.com> + + Copyright (C) 2022 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_CHILD_H__ +#define __PASSKEY_CHILD_H__ + +#include <fido.h> + +#define DEFAULT_PROMPT "Insert your passkey device, then press ENTER." +#define DEFAULT_CUE "Please touch the device." + +#define DEVLIST_SIZE 64 +#define USER_ID_SIZE 32 +#define TIMEOUT 15 +#define FREQUENCY 1 + +enum action_opt { + ACTION_NONE, + ACTION_REGISTER, + ACTION_AUTHENTICATE, + ACTION_GET_ASSERT, + ACTION_VERIFY_ASSERT +}; + +enum credential_type { + CRED_SERVER_SIDE, + CRED_DISCOVERABLE +}; + +struct passkey_data { + enum action_opt action; + const char *shortname; + const char *domain; + char **key_handle_list; + int key_handle_size; + char **public_key_list; + int public_key_size; + const char *crypto_challenge; + const char *auth_data; + const char *signature; + int type; + fido_opt_t user_verification; + enum credential_type cred_type; + unsigned char *user_id; + char *mapping_file; + bool quiet; + bool debug_libfido2; +}; + +struct pk_data_t { + void *public_key; + int type; +}; + +/** + * @brief Parse arguments + * + * @param[in] mem_ctx Memory context + * @param[in] argc Number of arguments + * @param[in] argv Argument list + * @param[out] data passkey data + * + * @return 0 if the arguments were parsed properly, + * another value on error. + */ +errno_t +parse_arguments(TALLOC_CTX *mem_ctx, int argc, const char *argv[], + struct passkey_data *data); + +/** + * @brief Check that all the arguments have been set + * + * @param[in] data passkey data + * + * @return 0 if the arguments were set properly, + * another value on error. + */ +errno_t +check_arguments(const struct passkey_data *data); + +/** + * @brief Register a key for a user + * + * @param[in] data passkey data + * + * @return 0 if the key was registered properly, + * another value on error. + */ +errno_t +register_key(struct passkey_data *data); + +/** + * @brief Translate COSE type from string to int + * + * @param[in] type string COSE type + * @param[out] out int COSE type + * + * @return 0 if the COSE type has been translated, + * another value if the COSE type doesn't exist. + */ +errno_t +cose_str_to_int(const char *type, int *out); + +/** + * @brief Prepare user credentials + * + * @param[in] data passkey data + * @param[in] dev Device information + * @param[out] cred Credentials + * + * @return 0 if the credentials were prepared properly, + * another value on error. + */ +errno_t +prepare_credentials(struct passkey_data *data, fido_dev_t *dev, + fido_cred_t *cred); + +/** + * @brief List connected passkey devices + * + * @param[out] dev_list passkey device list + * @param[out] dev_number Number of passkey devices + * + * @return 0 if the list was retrieved properly, another value on error. + */ +errno_t +list_devices(fido_dev_info_t *dev_list, size_t *dev_number); + +/** + * @brief Select passkey device + * + * @param[in] action Action to perform with the key + * @param[in] dev_list passkey device list + * @param[in] dev_index passkey device index + * @param[in] assert Assert + * @param[out] dev Device information + * + * @return 0 if the device was opened properly, another value on error. + */ +errno_t +select_device(enum action_opt action, fido_dev_info_t *dev_list, + size_t dev_list_len, fido_assert_t *assert, + fido_dev_t **_dev); + +/** + * @brief Get authenticator data from assert + * + * @param[in] dev_list passkey device list + * @param[in] dev_list_len passkey device list length + * @param[in] assert Assert + * @param[out] dev Authenticator data + * + * @return 0 if the authenticator data was retrieved properly, + * another value on error. + */ +errno_t +select_from_multiple_devices(fido_dev_info_t *dev_list, + size_t dev_list_len, + fido_assert_t *assert, + fido_dev_t **_dev); + +/** + * @brief Receive PIN via stdin + * + * @param[in] mem_ctx Memory context + * @param[in] fd File descriptor + * @param[out] pin Pin + * + * @return 0 if the authenticator data was received properly, + * error code otherwise. + */ +errno_t +passkey_recv_pin(TALLOC_CTX *mem_ctx, int fd, char **_pin); + +/** + * @brief Disable echoing and read PIN from stdin + * + * @param[out] line_ptr PIN + * + * @return Number of bytes read, or -1 on error. + */ +ssize_t +read_pin(char **line_ptr); + +/** + * @brief Generate passkey credentials + * + * @param[in] data passkey data + * @param[in] dev Device information + * @param[out] cred Credentials + * + * @return 0 if the credentials were generated properly, + * another value on error. + */ +errno_t +generate_credentials(struct passkey_data *data, fido_dev_t *dev, + fido_cred_t *cred); + +/** + * @brief Verify passkey credentials + * + * @param[in] cred Credentials + * + * @return 0 if the credentials were verified properly, + * another value on error. + */ +errno_t +verify_credentials(const fido_cred_t *const cred); + +/** + * @brief Print passkey credentials + * + * @param[in] data passkey data + * @param[out] cred Credentials + * + * @return 0 if the credentials were printed properly, + * another value on error. + */ +errno_t +print_credentials(const struct passkey_data *data, + const fido_cred_t *const cred); + +/** + * @brief Print passkey credentials + * + * @param[in] data passkey data + * @param[in] b64_cred_id Credential ID in b64 + * @param[in] pem_key Public key in PEM format + * + * @return 0 if the credentials were printed properly, + * another value on error. + */ +errno_t +print_credentials_to_file(const struct passkey_data *data, + const char *b64_cred_id, + const char *pem_key); + +/** + * @brief Format libfido2's es256 data structure to EVP_PKEY + * + * @param[in] mem_ctx Memory context + * @param[in] es256_key Public key pointer + * @param[in] es256_key_len Public key length + * @param[out] _evp_pkey Pointer to public key structure + * + * @return 0 if the key was formatted properly, error code otherwise. + */ +int +es256_pubkey_to_evp_pkey(TALLOC_CTX *mem_ctx, const void *es256_key, + size_t es256_key_len, EVP_PKEY **_evp_pkey); + +/** + * @brief Format libfido2's rs256 data structure to EVP_PKEY + * + * @param[in] mem_ctx Memory context + * @param[in] rs256_key Public key pointer + * @param[in] rs256_key_len Public key length + * @param[out] _evp_pkey Pointer to public key structure + * + * @return 0 if the key was formatted properly, error code otherwise. + */ +int +rs256_pubkey_to_evp_pkey(TALLOC_CTX *mem_ctx, const void *rs256_key, + size_t rs256_key_len, EVP_PKEY **_evp_pkey); + +/** + * @brief Format libfido2's eddsa data structure to EVP_PKEY + * + * @param[in] mem_ctx Memory context + * @param[in] eddsa_key Public key pointer + * @param[in] eddsa_key_len Public key length + * @param[out] _evp_pkey Pointer to public key structure + * + * @return 0 if the key was formatted properly, error code otherwise. + */ +int +eddsa_pubkey_to_evp_pkey(TALLOC_CTX *mem_ctx, const void *eddsa_key, + size_t eddsa_key_len, EVP_PKEY **_evp_pkey); + +/** + * @brief Format the public key to base64 + * + * @param[in] mem_ctx Memory context + * @param[in] data passkey data + * @param[in] public_key Public key + * @param[in] pk_len Public key length + * @param[out] _pem_key Public key in PEM format + * + * @return 0 if the key was formatted properly, error code otherwise. + */ +errno_t +public_key_to_base64(TALLOC_CTX *mem_ctx, const struct passkey_data *data, + const unsigned char *public_key, size_t pk_len, + char **_pem_key); + +/* + * @brief Authenticate a user + * + * Prepare the assertion request data, select the device to use, get the device + * options and compare them with the organization policy, decode the public + * key, request the assert and verify it. + * + * @param[in] data passkey data + * + * @return 0 if the user was authenticated properly, + * error code otherwise. + */ +errno_t +authenticate(struct passkey_data *data); + +/* + * @brief Select authenticator for verification + * + * + * @param[in] data passkey data + * @param[out] _dev Device information + * @param[out] _assert Assert + * @param[out] _index Index for key handle list + * + * @return 0 if the authenticator was selected properly, + * error code otherwise. + */ +errno_t +select_authenticator(struct passkey_data *data, fido_dev_t **_dev, + fido_assert_t **_assert, int *_index); + +/** + * @brief Set client data hash in the assert + * + * @param[in] data passkey data + * @param[in,out] _assert Assert + * + * @return 0 if the data was set properly, + * error code otherwise. + */ +errno_t +set_assert_client_data_hash(const struct passkey_data *data, + fido_assert_t *_assert); + +/** + * @brief Set authenticator data and signature in the assert + * + * @param[in] data passkey data + * @param[in,out] _assert Assert + * + * @return 0 if the data was set properly, + * error code otherwise. + */ +errno_t +set_assert_auth_data_signature(const struct passkey_data *data, + fido_assert_t *_assert); + +/** + * @brief Set options in the assert + * + * @param[in] up User presence check + * @param[in] uv User verification check + * @param[out] assert Assert + * + * @return 0 if the data was set properly, + * error code otherwise. + */ +errno_t +set_assert_options(fido_opt_t up, fido_opt_t uv, fido_assert_t *_assert); + +/** + * @brief Get authentication data and signature from assert + * + * @param[in] mem_ctx Memory context + * @param[in] assert Assert + * @param[out] _auth_data Authentication data + * @param[out] _signature Signature + * + * @return 0 if the data was get properly, + * error code otherwise. + */ +errno_t +get_assert_auth_data_signature(TALLOC_CTX *mem_ctx, fido_assert_t *assert, + const char **_auth_data, + const char **_signature); + +/** + * @brief Prepare assert + * + * @param[in] data passkey data + * @param[in] index Index for key handle list + * @param[in,out] _assert Assert + * + * @return 0 if the assert was prepared properly, + * error code otherwise. + */ +errno_t +prepare_assert(const struct passkey_data *data, int index, + fido_assert_t *_assert); + +/** + * @brief Reset and free public key + * + * @param[out] _pk_data Public key data + * + * @return 0 if the public key was reset properly, + * error code otherwise. + */ +errno_t +reset_public_key(struct pk_data_t *_pk_data); + +/** + * @brief Format EVP_PKEY to libfido2's es256 data structure + * + * @param[in] evp_pkey EVP_PKEY public key + * @param[out] _pk_data Public key data + * + * @return 0 if the public key was formatted properly, + * error code otherwise. + */ +errno_t +evp_pkey_to_es256_pubkey(const EVP_PKEY *evp_pkey, struct pk_data_t *_pk_data); + +/** + * @brief Format EVP_PKEY to libfido2's rs256 data structure + * + * @param[in] evp_pkey EVP_PKEY public key + * @param[out] _pk_data Public key data + * + * @return 0 if the public key was formatted properly, + * error code otherwise. + */ +errno_t +evp_pkey_to_rs256_pubkey(const EVP_PKEY *evp_pkey, struct pk_data_t *_pk_data); + +/** + * @brief Format EVP_PKEY to libfido2's eddsa data structure + * + * @param[in] evp_pkey EVP_PKEY public key + * @param[out] _pk_data Public key data + * + * @return 0 if the public key was formatted properly, + * error code otherwise. + */ +errno_t +evp_pkey_to_eddsa_pubkey(const EVP_PKEY *evp_pkey, struct pk_data_t *_pk_data); + +/** + * @brief Format the public key to the libfido2 data structure + * + * @param[in] pem_public_key PEM formatter public key + * @param[out] _pk_data Public key data + * + * @return 0 if the public key was formatted properly, + * error code otherwise. + */ +errno_t +public_key_to_libfido2(const char *pem_public_key, struct pk_data_t *_pk_data); + +/** + * @brief Get device options and compare with the policy options expectations + * + * @param[in] dev Device information + * @param[out] data passkey data + * + * @return 0 if the device data was retrieved and the options match properly, + * error code otherwise. + */ +errno_t +get_device_options(fido_dev_t *dev, struct passkey_data *_data); + +/** + * @brief Get assertion data + * + * @param[in] data passkey data + * @param[in] dev Device information + * @param[out] assert Assert + * + * @return 0 if the assertion was verified properly, + * error code otherwise. + */ +errno_t +request_assert(struct passkey_data *data, fido_dev_t *dev, + fido_assert_t *_assert); + +/** + * @brief Verify assertion + * + * @param[in] pk_data Public key data + * @param[in] assert Assert + * + * @return 0 if the assertion was verified properly, + * error code otherwise. + */ +errno_t +verify_assert(struct pk_data_t *data, fido_assert_t *assert); + +/** + * @brief Print assert request data in JSON format + * + * @param[in] key_handle Key handle + * @param[in] crypto_challenge Cryptographic challenge + * @param[in] auth_data Authenticator data + * @param[in] signature Assertion signature + * + */ +void +print_assert_data(const char *key_handle, const char *crypto_challenge, + const char *auth_data, const char *signature); + +/** + * @brief Obtain assertion data + * + * Prepare the assertion request data, select the device to use, select the + * authenticator, get the device options and compare them with the organization + * policy, request the assert, get the authenticator data, get the signature + * and print this all information. + * + * @param[in] data passkey data + * + * @return 0 if the assertion was obtained properly, + * error code otherwise. + */ +errno_t +get_assert_data(struct passkey_data *data); + +/** + * @brief Verify assertion data + * + * Prepare the assertion data, including the authenticator data and the + * signature; decode the public key and verify the assertion. + * + * @param[in] data passkey data + * + * @return 0 if the assertion was obtained properly, + * error code otherwise. + */ +errno_t +verify_assert_data(struct passkey_data *data); + +#endif /* __PASSKEY_CHILD_H__ */ diff --git a/src/passkey_child/passkey_child_assert.c b/src/passkey_child/passkey_child_assert.c new file mode 100644 index 0000000..5139dc8 --- /dev/null +++ b/src/passkey_child/passkey_child_assert.c @@ -0,0 +1,448 @@ +/* + SSSD + + Helper child to commmunicate with passkey devices + + Authors: + Iker Pedrosa <ipedrosa@redhat.com> + + Copyright (C) 2022 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 <jansson.h> +#include <termios.h> +#include <stdio.h> +#include <fido/es256.h> +#include <fido/rs256.h> +#include <fido/eddsa.h> + +#include "util/crypto/sss_crypto.h" +#include "util/debug.h" +#include "util/util.h" + +#include "passkey_child.h" + +errno_t +set_assert_client_data_hash(const struct passkey_data *data, + fido_assert_t *_assert) +{ + TALLOC_CTX *tmp_ctx = NULL; + unsigned char cdh[32]; + unsigned char *crypto_challenge = NULL; + size_t crypto_challenge_len; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_new() failed.\n"); + return ENOMEM; + } + + if (data->action == ACTION_AUTHENTICATE) { + ret = sss_generate_csprng_buffer(cdh, sizeof(cdh)); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sss_generate_csprng_buffer failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + + ret = fido_assert_set_clientdata_hash(_assert, cdh, sizeof(cdh)); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, + "fido_assert_set_clientdata_hash failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + } else { + crypto_challenge = sss_base64_decode(tmp_ctx, data->crypto_challenge, + &crypto_challenge_len); + if (crypto_challenge == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "failed to decode client data hash.\n"); + ret = ENOMEM; + goto done; + } + + if (crypto_challenge_len != 32) { + DEBUG(SSSDBG_OP_FAILURE, + "cryptographic-challenge length [%ld] must be 32.\n", + crypto_challenge_len); + ret = EINVAL; + goto done; + } + + ret = fido_assert_set_clientdata_hash(_assert, crypto_challenge, + crypto_challenge_len); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, + "fido_assert_set_clientdata_hash failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + } + +done: + talloc_free(tmp_ctx); + + return ret; +} + +errno_t +set_assert_options(fido_opt_t up, fido_opt_t uv, fido_assert_t *_assert) +{ + errno_t ret; + + ret = fido_assert_set_up(_assert, up); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, + "fido_assert_set_up failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + + ret = fido_assert_set_uv(_assert, uv); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, + "fido_assert_set_uv failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + +done: + return ret; +} + +errno_t +get_assert_auth_data_signature(TALLOC_CTX *mem_ctx, fido_assert_t *assert, + const char **_auth_data, + const char **_signature) +{ + TALLOC_CTX *tmp_ctx = NULL; + const unsigned char *auth_data; + const unsigned char *signature; + const char *b64_auth_data; + const char *b64_signature; + size_t auth_data_len; + size_t signature_len; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_new() failed.\n"); + return ENOMEM; + } + + auth_data = fido_assert_authdata_ptr(assert, 0); + if (auth_data == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "fido_assert_authdata_ptr failed.\n"); + ret = ENOMEM; + goto done; + } + + auth_data_len = fido_assert_authdata_len(assert, 0); + if (auth_data_len == 0) { + DEBUG(SSSDBG_OP_FAILURE, + "fido_assert_authdata_len failed.\n"); + ret = ENOMEM; + goto done; + } + + b64_auth_data = sss_base64_encode(tmp_ctx, auth_data, auth_data_len); + if (b64_auth_data == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "failed to encode authenticator data.\n"); + ret = ENOMEM; + goto done; + } + + signature = fido_assert_sig_ptr(assert, 0); + if (signature == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "fido_assert_sig_ptr failed.\n"); + ret = ENOMEM; + goto done; + } + + signature_len = fido_assert_sig_len(assert, 0); + if (signature_len == 0) { + DEBUG(SSSDBG_OP_FAILURE, + "fido_assert_sig_len failed.\n"); + ret = ENOMEM; + goto done; + } + + b64_signature = sss_base64_encode(tmp_ctx, signature, signature_len); + if (b64_signature == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "failed to encode signature.\n"); + ret = ENOMEM; + goto done; + } + + *_auth_data = talloc_steal(mem_ctx, b64_auth_data); + *_signature = talloc_steal(mem_ctx, b64_signature); + ret = EOK; + +done: + talloc_free(tmp_ctx); + + return ret; +} + +errno_t +set_assert_auth_data_signature(const struct passkey_data *data, + fido_assert_t *_assert) +{ + TALLOC_CTX *tmp_ctx = NULL; + const unsigned char *auth_data = NULL; + const unsigned char *signature = NULL; + size_t auth_data_len; + size_t signature_len; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + ret = ENOMEM; + goto done; + } + + ret = fido_assert_set_count(_assert, 1); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, + "fido_assert_set_count failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + + auth_data = sss_base64_decode(tmp_ctx, data->auth_data, &auth_data_len); + if (auth_data == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "failed to decode authenticator data.\n"); + ret = ENOMEM; + goto done; + } + + ret = fido_assert_set_authdata(_assert, 0, auth_data, auth_data_len); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, + "fido_assert_set_authdata failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + + signature = sss_base64_decode(tmp_ctx, data->signature, &signature_len); + if (signature == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "failed to decode signature.\n"); + ret = ENOMEM; + goto done; + } + + ret = fido_assert_set_sig(_assert, 0, signature, signature_len); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, + "fido_assert_set_sig failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + +done: + talloc_free(tmp_ctx); + + return ret; +} + +errno_t +prepare_assert(const struct passkey_data *data, int index, + fido_assert_t *_assert) +{ + TALLOC_CTX *tmp_ctx = NULL; + unsigned char *key_handle; + size_t key_handle_len; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + ret = ENOMEM; + goto done; + } + + ret = fido_assert_set_rp(_assert, data->domain); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, + "fido_assert_set_rp failed [%d]: %s.\n", ret, fido_strerr(ret)); + goto done; + } + + key_handle = sss_base64_decode(tmp_ctx, data->key_handle_list[index], + &key_handle_len); + if (key_handle == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to decode key handle.\n"); + ret = ENOMEM; + goto done; + } + + ret = fido_assert_allow_cred(_assert, key_handle, key_handle_len); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, + "fido_assert_allow_cred failed [%d]: %s.\n", ret, fido_strerr(ret)); + goto done; + } + + ret = set_assert_options(FIDO_OPT_FALSE, FIDO_OPT_OMIT, _assert); + if (ret != FIDO_OK) { + goto done; + } + + ret = set_assert_client_data_hash(data, _assert); + if (ret != EOK) { + goto done; + } + +done: + talloc_free(tmp_ctx); + + return ret; +} + +errno_t +reset_public_key(struct pk_data_t *_pk_data) +{ + if (_pk_data->type == COSE_ES256) { + es256_pk_free((es256_pk_t **) &_pk_data->public_key); + } else if (_pk_data->type == COSE_RS256) { + rs256_pk_free((rs256_pk_t **) &_pk_data->public_key); + } else if (_pk_data->type == COSE_EDDSA) { + eddsa_pk_free((eddsa_pk_t **) &_pk_data->public_key); + } + + return EOK; +} + +errno_t +request_assert(struct passkey_data *data, fido_dev_t *dev, + fido_assert_t *_assert) +{ + TALLOC_CTX *tmp_ctx = NULL; + char *pin = NULL; + bool has_pin; + bool has_uv; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_new() failed.\n"); + return ENOMEM; + } + + has_pin = fido_dev_has_pin(dev); + has_uv = fido_dev_has_uv(dev); + if (has_uv == true && data->user_verification != FIDO_OPT_FALSE) { + ret = fido_dev_get_assert(dev, _assert, NULL); + if (ret != FIDO_OK && has_pin == true) { + DEBUG(SSSDBG_OP_FAILURE, + "fido_dev_get_assert failed [%d]: %s. " + "Falling back to PIN authentication.\n", + ret, fido_strerr(ret)); + } else if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, "fido_dev_get_assert failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } else { + DEBUG(SSSDBG_TRACE_FUNC, "fido_dev_get_assert succeeded.\n"); + goto done; + } + } + + if (has_pin == true && data->user_verification != FIDO_OPT_FALSE) { + ret = passkey_recv_pin(tmp_ctx, STDIN_FILENO, &pin); + if (ret != EOK) { + goto done; + } + } + + ret = fido_dev_get_assert(dev, _assert, pin); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, "fido_dev_get_assert failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + + ret = fido_assert_set_uv(_assert, data->user_verification); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, + "fido_assert_set_uv failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + +done: + if (pin != NULL) { + sss_erase_mem_securely(pin, strlen(pin)); + } + talloc_free(tmp_ctx); + + return ret; +} + +errno_t +verify_assert(struct pk_data_t *pk_data, fido_assert_t *assert) +{ + errno_t ret; + + ret = fido_assert_verify(assert, 0, pk_data->type, pk_data->public_key); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, "fido_assert_verify failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + +done: + return ret; +} + +void +print_assert_data(const char *key_handle, const char *crypto_challenge, + const char *auth_data, const char *signature) +{ + json_t *passkey = NULL; + char* string = NULL; + + /* Kerberos expects the user_id field, thus it cannot be removed and there + * is nothing to set so it's an empty string. + */ + passkey = json_pack("{s:s*, s:s*, s:s*, s:s*, s:s*}", + "credential_id", key_handle, + "cryptographic_challenge", crypto_challenge, + "authenticator_data", auth_data, + "assertion_signature", signature, + "user_id", ""); + if (passkey == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to create passkey object.\n"); + goto done; + } + + string = json_dumps(passkey, 0); + if (string == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "json_dumps() failed.\n"); + goto done; + } + + puts(string); + free(string); + +done: + json_decref(passkey); + + return; +} diff --git a/src/passkey_child/passkey_child_common.c b/src/passkey_child/passkey_child_common.c new file mode 100644 index 0000000..000a7ee --- /dev/null +++ b/src/passkey_child/passkey_child_common.c @@ -0,0 +1,885 @@ +/* + SSSD + + Helper child to commmunicate with passkey devices + + Authors: + Iker Pedrosa <ipedrosa@redhat.com> + + Copyright (C) 2022 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 <popt.h> +#include <sys/prctl.h> +#include <fido/param.h> +#include <openssl/err.h> +#include <openssl/pem.h> + +#include <openssl/err.h> +#include <openssl/pem.h> + +#include "util/crypto/sss_crypto.h" +#include "util/debug.h" +#include "util/util.h" +#include "util/crypto/sss_crypto.h" + +#include "passkey_child.h" + +#if OPENSSL_VERSION_NUMBER >= 0x30000000 +#define get_id(x) EVP_PKEY_get_base_id((x)) +#else +#define get_id(x) EVP_PKEY_base_id((x)) +#endif /* OPENSSL_VERSION_NUMBER */ + +errno_t +cose_str_to_int(const char *type, int *out) +{ + if (strcasecmp(type, "es256") == 0) { + *out = COSE_ES256; + } else if (strcasecmp(type, "rs256") == 0) { + *out = COSE_RS256; + } else if (strcasecmp(type, "eddsa") == 0) { + *out = COSE_EDDSA; + } else { + *out = 0; + return ERR_INVALID_CRED_TYPE; + } + + return EOK; +} + +static errno_t +cred_type_str_to_enum(const char *type, enum credential_type *out) +{ + if (strcasecmp(type, "server-side") == 0) { + *out = CRED_SERVER_SIDE; + } else if (strcasecmp(type, "discoverable") == 0) { + *out = CRED_DISCOVERABLE; + } else { + *out = 0; + return ERR_INVALID_CRED_TYPE; + } + + return EOK; +} + +static errno_t +parse_public_keys_and_handlers(TALLOC_CTX *mem_ctx, + const char *public_keys, + const char *key_handles, + struct passkey_data *_data) +{ + TALLOC_CTX *tmp_ctx = NULL; + char **pk_list = NULL; + char **kh_list = NULL; + int pk_num = 0; + int kh_num = 0; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + ERROR("talloc_new() failed\n"); + return ENOMEM; + } + + ret = split_on_separator(tmp_ctx, public_keys, ',', true, true, &pk_list, &pk_num); + if (ret != EOK && _data->action == ACTION_AUTHENTICATE) { + ERROR("Incorrectly formatted public keys.\n"); + goto done; + } + + ret = split_on_separator(tmp_ctx, key_handles, ',', true, true, &kh_list, &kh_num); + if (ret != EOK) { + ERROR("Incorrectly formatted public keys.\n"); + goto done; + } + + if (_data->action == ACTION_AUTHENTICATE && pk_num != kh_num) { + ERROR("The number of public keys and key handles don't match.\n"); + goto done; + } + + _data->public_key_list = talloc_steal(mem_ctx, pk_list); + _data->key_handle_list = talloc_steal(mem_ctx, kh_list); + _data->public_key_size = pk_num; + _data->key_handle_size = kh_num; + +done: + talloc_free(tmp_ctx); + + return ret; +} + +errno_t +parse_arguments(TALLOC_CTX *mem_ctx, int argc, const char *argv[], + struct passkey_data *data) +{ + int opt; + int dumpable = 1; + int debug_fd = -1; + char *user_verification = NULL; + char *public_keys = NULL; + char *key_handles = NULL; + const char *opt_logger = NULL; + const char *type = NULL; + const char *cred_type = NULL; + poptContext pc; + errno_t ret; + + /* Set defaults */ + data->action = ACTION_NONE; + data->shortname = NULL; + data->domain = NULL; + data->public_key_list = NULL; + data->key_handle_list = NULL; + data->public_key_size = 0; + data->key_handle_size = 0; + data->crypto_challenge = NULL; + data->auth_data = NULL; + data->signature = NULL; + data->type = COSE_ES256; + data->user_verification = FIDO_OPT_OMIT; + data->cred_type = CRED_SERVER_SIDE; + data->user_id = NULL; + data->mapping_file = NULL; + data->quiet = false; + data->debug_libfido2 = false; + + 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 + {"register", 0, POPT_ARG_NONE, NULL, 'r', + _("Register a passkey for a user"), NULL }, + {"authenticate", 0, POPT_ARG_NONE, NULL, 'a', + _("Authenticate a user with a passkey"), NULL }, + {"get-assert", 0, POPT_ARG_NONE, NULL, 'g', + _("Obtain assertion data"), NULL }, + {"verify-assert", 0, POPT_ARG_NONE, NULL, 'v', + _("Verify assertion data"), NULL }, + {"username", 0, POPT_ARG_STRING, &data->shortname, 0, + _("Shortname"), NULL }, + {"domain", 0, POPT_ARG_STRING, &data->domain, 0, + _("Domain"), NULL}, + {"public-key", 0, POPT_ARG_STRING, &public_keys, 0, + _("Public key"), NULL }, + {"key-handle", 0, POPT_ARG_STRING, &key_handles, 0, + _("Key handle"), NULL}, + {"cryptographic-challenge", 0, POPT_ARG_STRING, + &data->crypto_challenge, 0, + _("Cryptographic challenge"), NULL}, + {"auth-data", 0, POPT_ARG_STRING, &data->auth_data, 0, + _("Authenticator data"), NULL}, + {"signature", 0, POPT_ARG_STRING, &data->signature, 0, + _("Signature"), NULL}, + {"type", 0, POPT_ARG_STRING, &type, 0, + _("COSE type to use"), "es256|rs256|eddsa"}, + {"user-verification", 0, POPT_ARG_STRING, &user_verification, 0, + _("Require user-verification"), "true|false"}, + {"cred-type", 0, POPT_ARG_STRING, &cred_type, 0, + _("Credential type"), "server-side|discoverable"}, + {"output-file", 0, POPT_ARG_STRING, &data->mapping_file, 0, + _("Write key mapping data to file"), NULL}, + {"quiet", 0, POPT_ARG_NONE, NULL, 'q', + _("Supress prompts"), NULL}, + {"debug-libfido2", 0, POPT_ARG_NONE, NULL, 'd', + _("Enable debug in libfido2 library"), NULL}, + SSSD_LOGGER_OPTS + POPT_TABLEEND + }; + + /* Set debug level to invalid value so we can decide if -d 0 was used. */ + debug_level = SSSDBG_INVALID; + + pc = poptGetContext(argv[0], argc, argv, long_options, 0); + + while((opt = poptGetNextOpt(pc)) != -1) { + switch(opt) { + case 'r': + if (data->action != ACTION_NONE + && data->action != ACTION_REGISTER) { + fprintf(stderr, "\nActions are mutually exclusive and should" \ + " be used only once.\n\n"); + poptPrintUsage(pc, stderr, 0); + ret = EINVAL; + goto done; + } + data->action = ACTION_REGISTER; + break; + case 'a': + if (data->action != ACTION_NONE + && data->action != ACTION_AUTHENTICATE) { + fprintf(stderr, "\nActions are mutually exclusive and should" \ + " be used only once.\n\n"); + poptPrintUsage(pc, stderr, 0); + ret = EINVAL; + goto done; + } + data->action = ACTION_AUTHENTICATE; + break; + case 'g': + if (data->action != ACTION_NONE + && data->action != ACTION_GET_ASSERT) { + fprintf(stderr, "\nActions are mutually exclusive and should" \ + " be used only once.\n\n"); + poptPrintUsage(pc, stderr, 0); + ret = EINVAL; + goto done; + } + data->action = ACTION_GET_ASSERT; + break; + case 'v': + if (data->action != ACTION_NONE + && data->action != ACTION_VERIFY_ASSERT) { + fprintf(stderr, "\nActions are mutually exclusive and should" \ + " be used only once.\n\n"); + poptPrintUsage(pc, stderr, 0); + ret = EINVAL; + goto done; + } + data->action = ACTION_VERIFY_ASSERT; + break; + case 'q': + data->quiet = true; + break; + case 'd': + data->debug_libfido2 = true; + break; + default: + fprintf(stderr, "\nInvalid option %s: %s\n\n", + poptBadOption(pc, 0), poptStrerror(opt)); + poptPrintUsage(pc, stderr, 0); + ret = EINVAL; + goto done; + } + } + + poptFreeContext(pc); + + prctl(PR_SET_DUMPABLE, (dumpable == 0) ? 0 : 1); + + if (user_verification != NULL) { + if (strcmp(user_verification, "true") == 0) { + data->user_verification = FIDO_OPT_TRUE; + } else if (strcmp(user_verification, "false") == 0) { + data->user_verification = FIDO_OPT_FALSE; + } else if (user_verification != NULL) { + ERROR("[%s] is not a valid user-verification value.\n", + user_verification); + ret = EINVAL; + goto done; + } + } + + if (type != NULL) { + ret = cose_str_to_int(type, &data->type); + if (ret != EOK) { + ERROR("[%s] is not a valid COSE type (es256, rs256 or eddsa).\n", + type); + goto done; + } + } + + if (public_keys != NULL || key_handles != NULL) { + ret = parse_public_keys_and_handlers(mem_ctx, public_keys, key_handles, + data); + if (ret != EOK) { + goto done; + } + } + + if (cred_type != NULL) { + ret = cred_type_str_to_enum(cred_type, &data->cred_type); + if (ret != EOK) { + ERROR("[%s] is not a valid credential type (server-side or" + " discoverable).\n", + cred_type); + goto done; + } + } + + debug_prg_name = talloc_asprintf(NULL, "passkey_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"); + } + } + + DEBUG_INIT(debug_level, opt_logger); + + ret = EOK; + +done: + return ret; +} + +errno_t +check_arguments(const struct passkey_data *data) +{ + errno_t ret = EOK; + + DEBUG(SSSDBG_TRACE_FUNC, "Argument values after parsing\n"); + DEBUG(SSSDBG_TRACE_FUNC, "action: %d\n", data->action); + DEBUG(SSSDBG_TRACE_FUNC, "shortname: %s, domain: %s\n", + data->shortname, data->domain); + DEBUG(SSSDBG_TRACE_FUNC, "Number of key handles %d\n", + data->key_handle_size); + for (int i = 0; i < data->key_handle_size; i++) { + DEBUG(SSSDBG_TRACE_FUNC, "key %d, key_handle: %s\n", + i + 1, data->key_handle_list[i]); + } + DEBUG(SSSDBG_TRACE_FUNC, "Number of public keys %d\n", + data->public_key_size); + for (int i = 0; i < data->public_key_size; i++) { + DEBUG(SSSDBG_TRACE_FUNC, "key %d, public_key: %s\n", + i + 1, data->public_key_list[i]); + } + DEBUG(SSSDBG_TRACE_FUNC, "cryptographic-challenge: %s\n", + data->crypto_challenge); + DEBUG(SSSDBG_TRACE_FUNC, "auth-data: %s\n", + data->auth_data); + DEBUG(SSSDBG_TRACE_FUNC, "signature: %s\n", + data->signature); + DEBUG(SSSDBG_TRACE_FUNC, "type: %d\n", data->type); + DEBUG(SSSDBG_TRACE_FUNC, "user_verification: %d\n", + data->user_verification); + DEBUG(SSSDBG_TRACE_FUNC, "cred_type: %d\n", + data->cred_type); + DEBUG(SSSDBG_TRACE_FUNC, "Mapping file: %s\n", data->mapping_file); + DEBUG(SSSDBG_TRACE_FUNC, "debug_libfido2: %d\n", data->debug_libfido2); + + if (data->action == ACTION_NONE) { + DEBUG(SSSDBG_OP_FAILURE, "No action set.\n"); + ret = ERR_INPUT_PARSE; + goto done; + } + + if (data->action == ACTION_REGISTER + && (data->shortname == NULL || data->domain == NULL)) { + DEBUG(SSSDBG_OP_FAILURE, "Too few arguments for register action.\n"); + ret = ERR_INPUT_PARSE; + goto done; + } + + if (data->action == ACTION_AUTHENTICATE + && (data->domain == NULL || data->public_key_list == NULL + || data->key_handle_list == NULL)) { + DEBUG(SSSDBG_OP_FAILURE, + "Too few arguments for authenticate action.\n"); + ret = ERR_INPUT_PARSE; + goto done; + } + + if (data->action == ACTION_GET_ASSERT + && (data->domain == NULL || data->key_handle_list == NULL + || data->crypto_challenge == NULL)) { + DEBUG(SSSDBG_OP_FAILURE, + "Too few arguments for get-assert action.\n"); + ret = ERR_INPUT_PARSE; + goto done; + } + + if (data->action == ACTION_VERIFY_ASSERT + && (data->domain == NULL || data->public_key_list == NULL + || data->key_handle_list == NULL || data->crypto_challenge == NULL + || data->auth_data == NULL || data->signature == NULL)) { + DEBUG(SSSDBG_OP_FAILURE, + "Too few arguments for verify-assert action.\n"); + ret = ERR_INPUT_PARSE; + goto done; + } + +done: + return ret; +} + +errno_t +register_key(struct passkey_data *data) +{ + TALLOC_CTX *tmp_ctx = NULL; + fido_cred_t *cred = NULL; + fido_dev_t *dev = NULL; + fido_dev_info_t *dev_list = NULL; + size_t dev_number = 0; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_new() failed.\n"); + return ENOMEM; + } + + data->user_id = talloc_array(tmp_ctx, unsigned char, USER_ID_SIZE); + if (data->user_id == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_array() failed.\n"); + ret = ENOMEM; + goto done; + } + + cred = fido_cred_new(); + if (cred == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "fido_cred_new failed.\n"); + ret = ENOMEM; + goto done; + } + + dev_list = fido_dev_info_new(DEVLIST_SIZE); + if (dev_list == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "fido_dev_info_new failed.\n"); + ret = ENOMEM; + goto done; + } + + ret = list_devices(dev_list, &dev_number); + if (ret != EOK) { + goto done; + } + + ret = select_device(data->action, dev_list, dev_number, NULL, &dev); + if (ret != EOK) { + goto done; + } + + ret = prepare_credentials(data, dev, cred); + if (ret != EOK) { + goto done; + } + + ret = generate_credentials(data, dev, cred); + if (ret != EOK) { + ERROR("A problem occurred while generating the credentials.\n"); + goto done; + } + + ret = verify_credentials(cred); + if (ret != EOK) { + goto done; + } + + ret = print_credentials(data, cred); + if (ret != EOK) { + goto done; + } + + ret = EOK; + +done: + fido_cred_free(&cred); + fido_dev_info_free(&dev_list, dev_number); + if (dev != NULL) { + fido_dev_close(dev); + } + fido_dev_free(&dev); + talloc_free(tmp_ctx); + + return ret; +} + +errno_t +public_key_to_base64(TALLOC_CTX *mem_ctx, const struct passkey_data *data, + const unsigned char *public_key, size_t pk_len, + char **_pem_key) +{ + EVP_PKEY *evp_pkey = NULL; + unsigned char *pub = NULL; + char *pem_key = NULL; + unsigned long err; + errno_t ret; + + if (_pem_key == NULL) { + ret = EINVAL; + goto done; + } + + switch (data->type) { + case COSE_ES256: + ret = es256_pubkey_to_evp_pkey(mem_ctx, public_key, pk_len, &evp_pkey); + break; + case COSE_RS256: + ret = rs256_pubkey_to_evp_pkey(mem_ctx, public_key, pk_len, &evp_pkey); + break; + case COSE_EDDSA: + ret = eddsa_pubkey_to_evp_pkey(mem_ctx, public_key, pk_len, &evp_pkey); + break; + default: + DEBUG(SSSDBG_OP_FAILURE, "Invalid key type.\n"); + ret = EINVAL; + break; + } + + if (ret != EOK) { + goto done; + } + + ret = i2d_PUBKEY(evp_pkey, &pub); + if (ret < 1) { + err = ERR_get_error(); + DEBUG(SSSDBG_OP_FAILURE, "i2d_PUBKEY failed [%lu][%s].\n", + err, ERR_error_string(err, NULL)); + ret = EIO; + goto done; + } + + pem_key = sss_base64_encode(mem_ctx, pub, ret); + if (pem_key == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "sss_base64_encode failed.\n"); + ret = ENOMEM; + goto done; + } + + *_pem_key = pem_key; + ret = EOK; + +done: + free(pub); + + if (evp_pkey != NULL) { + EVP_PKEY_free(evp_pkey); + } + + return ret; +} + +errno_t +select_authenticator(struct passkey_data *data, fido_dev_t **_dev, + fido_assert_t **_assert, int *_index) +{ + fido_dev_info_t *dev_list = NULL; + fido_dev_t *dev = NULL; + size_t dev_list_len = 0; + fido_assert_t *assert = NULL; + int index = 0; + errno_t ret; + + dev_list = fido_dev_info_new(DEVLIST_SIZE); + if (dev_list == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "fido_dev_info_new failed.\n"); + ret = ENOMEM; + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Checking for devices.\n"); + ret = list_devices(dev_list, &dev_list_len); + if (ret != EOK) { + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "%d key handles provided.\n", + data->key_handle_size); + + while (index < data->key_handle_size) { + DEBUG(SSSDBG_TRACE_FUNC, + "Preparing assert request data with key handle %d.\n", index + 1); + + assert = fido_assert_new(); + if (assert == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "fido_assert_new failed.\n"); + ret = ENOMEM; + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Preparing assert request data.\n"); + ret = prepare_assert(data, index, assert); + if (ret != FIDO_OK) { + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Selecting device.\n"); + ret = select_device(data->action, dev_list, dev_list_len, assert, &dev); + if (ret == EOK) { + /* Key handle found in device */ + break; + } + + if (dev != NULL) { + fido_dev_close(dev); + } + fido_dev_free(&dev); + fido_assert_free(&assert); + index++; + } + + *_dev = dev; + *_assert = assert; + *_index = index; + +done: + if (ret != EOK) { + fido_assert_free(&assert); + } + fido_dev_info_free(&dev_list, dev_list_len); + + return ret; +} + +errno_t +public_key_to_libfido2(const char *pem_public_key, struct pk_data_t *_pk_data) +{ + TALLOC_CTX *tmp_ctx = NULL; + const unsigned char *public_key = NULL; + size_t pk_len; + const EVP_PKEY *evp_pkey = NULL; + int base_id; + unsigned long err; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + ret = ENOMEM; + goto done; + } + + public_key = sss_base64_decode(tmp_ctx, pem_public_key, &pk_len); + if (public_key == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to decode public key.\n"); + ret = ENOMEM; + goto done; + } + + evp_pkey = d2i_PUBKEY(NULL, &public_key, pk_len); + if (evp_pkey == NULL) { + err = ERR_get_error(); + DEBUG(SSSDBG_OP_FAILURE, "d2i_pubkey failed [%lu][%s].\n", + err, ERR_error_string(err, NULL)); + ret = EIO; + goto done; + } + + base_id = get_id(evp_pkey); + if (base_id == EVP_PKEY_EC) { + _pk_data->type = COSE_ES256; + ret = evp_pkey_to_es256_pubkey(evp_pkey, _pk_data); + } else if (base_id == EVP_PKEY_RSA) { + _pk_data->type = COSE_RS256; + ret = evp_pkey_to_rs256_pubkey(evp_pkey, _pk_data); + } else if (base_id == EVP_PKEY_ED25519) { + _pk_data->type = COSE_EDDSA; + ret = evp_pkey_to_eddsa_pubkey(evp_pkey, _pk_data); + } else { + DEBUG(SSSDBG_OP_FAILURE, + "Unrecognized key type.\n"); + ret = EINVAL; + } + if (ret != EOK) { + goto done; + } + + ret = EOK; + +done: + if (evp_pkey != NULL) { + EVP_PKEY_free(discard_const(evp_pkey)); + } + talloc_free(tmp_ctx); + + return ret; +} + +errno_t +authenticate(struct passkey_data *data) +{ + TALLOC_CTX *tmp_ctx = NULL; + fido_assert_t *assert = NULL; + fido_dev_t *dev = NULL; + struct pk_data_t pk_data = { 0 }; + int index; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + ERROR("talloc_new() failed\n"); + return ENOMEM; + } + + ret = select_authenticator(data, &dev, &assert, &index); + if (ret != EOK) { + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Comparing the device and policy options.\n"); + ret = get_device_options(dev, data); + if (ret != FIDO_OK) { + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Resetting assert options.\n"); + ret = set_assert_options(FIDO_OPT_TRUE, data->user_verification, assert); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to reset assert options.\n"); + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Resetting assert client data.\n"); + ret = set_assert_client_data_hash(data, assert); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to reset client data hash.\n"); + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Decoding public key.\n"); + ret = public_key_to_libfido2(data->public_key_list[index], &pk_data); + if (ret != FIDO_OK) { + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Getting assert.\n"); + ret = request_assert(data, dev, assert); + if (ret != FIDO_OK) { + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Verifying assert.\n"); + ret = verify_assert(&pk_data, assert); + if (ret != FIDO_OK) { + goto done; + } + + ret = FIDO_OK; + +done: + reset_public_key(&pk_data); + if (dev != NULL) { + fido_dev_close(dev); + } + fido_dev_free(&dev); + fido_assert_free(&assert); + talloc_free(tmp_ctx); + + return ret; +} + +errno_t +get_assert_data(struct passkey_data *data) +{ + TALLOC_CTX *tmp_ctx = NULL; + fido_dev_t *dev = NULL; + fido_assert_t *assert = NULL; + const char *auth_data = NULL; + const char *signature = NULL; + int index; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_new() failed.\n"); + return ENOMEM; + } + + ret = select_authenticator(data, &dev, &assert, &index); + if (ret != EOK) { + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Comparing the device and policy options.\n"); + ret = get_device_options(dev, data); + if (ret != FIDO_OK) { + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Resetting assert options.\n"); + ret = set_assert_options(FIDO_OPT_TRUE, data->user_verification, assert); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, "Failed to reset assert options.\n"); + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Getting assert.\n"); + ret = request_assert(data, dev, assert); + if (ret != FIDO_OK) { + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Getting authentication data and signature.\n"); + ret = get_assert_auth_data_signature(tmp_ctx, assert, &auth_data, + &signature); + if (ret != EOK) { + goto done; + } + + print_assert_data(data->key_handle_list[index], data->crypto_challenge, + auth_data, signature); + +done: + if (dev != NULL) { + fido_dev_close(dev); + } + fido_dev_free(&dev); + fido_assert_free(&assert); + talloc_free(tmp_ctx); + + return ret; +} + +errno_t +verify_assert_data(struct passkey_data *data) +{ + fido_assert_t *assert = NULL; + struct pk_data_t pk_data = { 0 }; + errno_t ret; + + assert = fido_assert_new(); + if (assert == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "fido_assert_new failed.\n"); + ret = ENOMEM; + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Preparing assert data.\n"); + ret = prepare_assert(data, 0, assert); + if (ret != FIDO_OK) { + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, + "Preparing assert authenticator data and signature.\n"); + ret = set_assert_auth_data_signature(data, assert); + if (ret != FIDO_OK) { + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Decoding public key.\n"); + ret = public_key_to_libfido2(data->public_key_list[0], &pk_data); + if (ret != FIDO_OK) { + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Verifying assert.\n"); + ret = verify_assert(&pk_data, assert); + if (ret != FIDO_OK) { + goto done; + } + + ret = FIDO_OK; + +done: + reset_public_key(&pk_data); + fido_assert_free(&assert); + + return ret; +} diff --git a/src/passkey_child/passkey_child_credentials.c b/src/passkey_child/passkey_child_credentials.c new file mode 100644 index 0000000..e27afb4 --- /dev/null +++ b/src/passkey_child/passkey_child_credentials.c @@ -0,0 +1,681 @@ +/* + SSSD + + Helper child to commmunicate with passkey devices + + Authors: + Iker Pedrosa <ipedrosa@redhat.com> + + Copyright (C) 2022 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 <fcntl.h> +#include <termios.h> +#include <stdio.h> + +#include <fido/es256.h> +#include <fido/rs256.h> +#include <fido/eddsa.h> + +#include "util/crypto/sss_crypto.h" +#include "util/debug.h" +#include "util/util.h" + +#include "passkey_child.h" + +#define IN_BUF_SIZE 1024 + +errno_t +prepare_credentials(struct passkey_data *data, fido_dev_t *dev, + fido_cred_t *cred) +{ + unsigned char cdh[32]; + fido_opt_t rk = FIDO_OPT_OMIT; + bool has_pin; + bool has_uv; + errno_t ret = EOK; + + ret = fido_cred_set_type(cred, data->type); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, "fido_cred_set_type failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + + ret = sss_generate_csprng_buffer(cdh, sizeof(cdh)); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sss_generate_csprng_buffer failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + + ret = fido_cred_set_clientdata_hash(cred, cdh, sizeof(cdh)); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, + "fido_cred_set_clientdata_hash failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Setting Relying Party ID and name to %s.\n", + data->domain); + + ret = fido_cred_set_rp(cred, data->domain, data->domain); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, "fido_cred_set_rp failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + + if (data->user_id == NULL) { + DEBUG(SSSDBG_OP_FAILURE, + "user_id must be allocated before using it.\n"); + ret = ENOMEM; + goto done; + } + + ret = sss_generate_csprng_buffer(data->user_id, USER_ID_SIZE); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "sss_generate_csprng_buffer failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + + DEBUG(SSSDBG_TRACE_FUNC, "Setting user: %s.\n", data->shortname); + + ret = fido_cred_set_user(cred, data->user_id, USER_ID_SIZE, + data->shortname, NULL, NULL); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, "fido_cred_set_user failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + + if (data->cred_type == CRED_DISCOVERABLE) { + rk = FIDO_OPT_TRUE; + } + + /* Set to FIDO_OPT_OMIT instead of FIDO_OPT_FALSE for compatibility reasons + */ + ret = fido_cred_set_rk(cred, rk); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, "fido_cred_set_rk failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + + has_uv = fido_dev_has_uv(dev); + has_pin = fido_dev_has_pin(dev); + if (data->user_verification == FIDO_OPT_TRUE && has_uv == false + && has_pin == false) { + DEBUG(SSSDBG_OP_FAILURE, + "Policy enabled user-verification but there isn't any " + "verification method set.\n"); + ret = EINVAL; + goto done; + } + + if (data->user_verification == FIDO_OPT_FALSE + && (has_uv == true || has_pin == true)) { + DEBUG(SSSDBG_CONF_SETTINGS, + "Policy disabled user-verification but the key settings are " + "enforcing it. Thus, enabling user-verification.\n"); + data->user_verification = FIDO_OPT_TRUE; + } + + if (has_uv == true) { + ret = fido_cred_set_uv(cred, FIDO_OPT_TRUE); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, "fido_cred_set_uv failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + } + +done: + return ret; +} + +errno_t +passkey_recv_pin(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; +} + +ssize_t +read_pin(char **pin) +{ + char *line_ptr = NULL; + struct termios old, new; + size_t line_len = 0; + ssize_t bytes_read; + ssize_t ret; + + ret = tcgetattr(STDIN_FILENO, &old); + if (ret != 0) { + DEBUG(SSSDBG_OP_FAILURE, + "Unable to get the parameters associated with stdin [%d]: %s.\n", + errno, sss_strerror(errno)); + goto done; + } + new = old; + new.c_lflag &= ~ECHO; + ret = tcsetattr(STDIN_FILENO, TCSAFLUSH, &new); + if (ret != 0) { + DEBUG(SSSDBG_OP_FAILURE, "Unable to turn echoing off [%d]: %s.\n", + errno, sss_strerror(errno)); + goto done; + } + + PRINT("Enter PIN:\n"); + fflush(stdout); + bytes_read = getline(&line_ptr, &line_len, stdin); + if (bytes_read == -1) { + DEBUG(SSSDBG_OP_FAILURE, "getline failed [%d]: %s.\n", + errno, sss_strerror(errno)); + } else { + /* Remove the end of line '\n' character */ + line_ptr[--bytes_read] = '\0'; + } + PRINT("\n"); + + ret = tcsetattr(STDIN_FILENO, TCSAFLUSH, &old); + if (ret != 0) { + DEBUG(SSSDBG_OP_FAILURE, + "Unable to restore parameters associated with stdin [%d]: %s.\n", + errno, sss_strerror(errno)); + goto done; + } + + ret = bytes_read; + *pin = line_ptr; + +done: + return ret; +} + +errno_t +generate_credentials(struct passkey_data *data, fido_dev_t *dev, + fido_cred_t *cred) +{ + TALLOC_CTX *tmp_ctx = NULL; + char *pin = NULL; + char *tmp_pin = NULL; + bool has_pin; + ssize_t pin_len = 0; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "talloc_new() failed.\n"); + return ENOMEM; + } + + has_pin = fido_dev_has_pin(dev); + if (has_pin == true) { + if (data->quiet == true) { + ret = passkey_recv_pin(tmp_ctx, STDIN_FILENO, &pin); + if (ret != EOK) { + goto done; + } + } else { + pin_len = read_pin(&tmp_pin); + if (pin_len == -1) { + ret = ERR_INPUT_PARSE; + goto done; + } + pin = talloc_strdup(tmp_ctx, tmp_pin); + sss_erase_mem_securely(tmp_pin, pin_len); + free(tmp_pin); + } + } + + if (data->quiet == false) { + PRINT("Please touch the device.\n"); + fflush(stdout); + } + ret = fido_dev_make_cred(dev, cred, pin); + sss_erase_mem_securely(pin, pin_len); + + if (ret != FIDO_OK) { + if (ret == FIDO_ERR_PIN_INVALID) { + ERROR("Invalid PIN.\n"); + } + DEBUG(SSSDBG_OP_FAILURE, "fido_dev_make_cred failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + + if (has_pin == true) { + ret = fido_cred_set_uv(cred, FIDO_OPT_TRUE); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, "fido_cred_set_uv failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + } + +done: + talloc_free(tmp_ctx); + + return ret; +} + +errno_t +verify_credentials(const fido_cred_t *const cred) +{ + errno_t ret; + + if (fido_cred_x5c_ptr(cred) != NULL) { + ret = fido_cred_verify(cred); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, "fido_cred_verify failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + } else { + DEBUG(SSSDBG_TRACE_FUNC, + "Attestation certificate missing. " + "Falling back to self attestation.\n"); + ret = fido_cred_verify_self(cred); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, + "fido_cred_verify_self failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + } + +done: + return ret; +} + +errno_t +print_credentials(const struct passkey_data *data, + const fido_cred_t *const cred) +{ + TALLOC_CTX *tmp_ctx = NULL; + const unsigned char *cred_id = NULL; + const unsigned char *public_key = NULL; + const char *b64_cred_id = NULL; + char *pem_key = NULL; + size_t cred_id_len; + size_t user_key_len; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + ret = ENOMEM; + goto done; + } + + cred_id = fido_cred_id_ptr(cred); + if (cred_id == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "fido_cred_id_ptr failed.\n"); + ret = ERR_CREDS_INVALID; + goto done; + } + + cred_id_len = fido_cred_id_len(cred); + if (cred_id_len == 0) { + DEBUG(SSSDBG_OP_FAILURE, "fido_cred_id_len returned 0.\n"); + ret = ERR_CREDS_INVALID; + goto done; + } + + b64_cred_id = sss_base64_encode(tmp_ctx, cred_id, cred_id_len); + if (b64_cred_id == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "failed to encode key handle.\n"); + ret = ENOMEM; + goto done; + } + + public_key = fido_cred_pubkey_ptr(cred); + if (public_key == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "fido_cred_pubkey_ptr failed.\n"); + ret = ERR_CREDS_INVALID; + goto done; + } + + user_key_len = fido_cred_pubkey_len(cred); + if (user_key_len == 0) { + DEBUG(SSSDBG_OP_FAILURE, "fido_cred_pubkey_len returned 0.\n"); + ret = ERR_CREDS_INVALID; + goto done; + } + + ret = public_key_to_base64(tmp_ctx, data, public_key, user_key_len, + &pem_key); + if (ret != EOK) { + DEBUG(SSSDBG_OP_FAILURE, + "failed to format public key to b64 [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + + PRINT("passkey:%s,%s\n", b64_cred_id, pem_key); + if (data->mapping_file != NULL) { + print_credentials_to_file(data, b64_cred_id, pem_key); + } + ret = EOK; + +done: + talloc_free(tmp_ctx); + + return ret; +} + +errno_t +print_credentials_to_file(const struct passkey_data *data, + const char *b64_cred_id, + const char *pem_key) +{ + TALLOC_CTX *tmp_ctx = NULL; + char *mapping_data = NULL; + int mapping_data_len = 0; + int fd = -1; + ssize_t written = 0; + errno_t ret; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + ret = ENOMEM; + goto done; + } + + mapping_data = talloc_asprintf(tmp_ctx, "passkey:%s,%s", + b64_cred_id, pem_key); + if (mapping_data == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_asprintf failed.\n"); + ret = ENOMEM; + goto done; + } + mapping_data_len = strlen(mapping_data); + + fd = open(data->mapping_file, O_WRONLY|O_CREAT, 0640); + if (fd == -1) { + ret = errno; + DEBUG(SSSDBG_OP_FAILURE, + "open() failed [%d][%s]\n", ret, strerror(ret)); + ret = EIO; + goto done; + } + + errno = 0; + written = sss_atomic_write_s(fd, mapping_data, mapping_data_len); + if (written == -1) { + ret = errno; + DEBUG(SSSDBG_OP_FAILURE, + "Write failed [%d][%s].\n", ret, strerror(ret)); + goto done; + } + + if (written != mapping_data_len) { + DEBUG(SSSDBG_OP_FAILURE, + "Write error, wrote [%zd] bytes, expected [%d]\n", + written, mapping_data_len); + ret = EIO; + goto done; + } + + ret = EOK; + +done: + if (fd != -1) { + if (close(fd) == -1) { + DEBUG(SSSDBG_OP_FAILURE, + "Close failed [%s].\n", strerror(errno)); + } + } + talloc_free(tmp_ctx); + + return ret; +} + +int +es256_pubkey_to_evp_pkey(TALLOC_CTX *mem_ctx, const void *es256_key, + size_t es256_key_len, EVP_PKEY **_evp_pkey) +{ + EVP_PKEY *evp_pkey = NULL; + es256_pk_t *public_key = NULL; + errno_t ret; + + public_key = es256_pk_new(); + if (public_key == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "es256_pk_new failed.\n"); + ret = ENOMEM; + goto done; + } + + ret = es256_pk_from_ptr(public_key, es256_key, es256_key_len); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, "es256_pk_from_ptr failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + + evp_pkey = es256_pk_to_EVP_PKEY(public_key); + if (evp_pkey == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "es256_pk_to_EVP_PKEY failed.\n"); + ret = ENOMEM; + goto done; + } + + *_evp_pkey = evp_pkey; + ret = EOK; + +done: + es256_pk_free(&public_key); + + return ret; +} + +int +rs256_pubkey_to_evp_pkey(TALLOC_CTX *mem_ctx, const void *rs256_key, + size_t rs256_key_len, EVP_PKEY **_evp_pkey) +{ + EVP_PKEY *evp_pkey = NULL; + rs256_pk_t *public_key = NULL; + errno_t ret; + + public_key = rs256_pk_new(); + if (public_key == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "rs256_pk_new failed.\n"); + ret = ENOMEM; + goto done; + } + + ret = rs256_pk_from_ptr(public_key, rs256_key, rs256_key_len); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, "rs256_pk_from_ptr failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + + evp_pkey = rs256_pk_to_EVP_PKEY(public_key); + if (evp_pkey == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "rs256_pk_to_EVP_PKEY failed.\n"); + ret = ENOMEM; + goto done; + } + + *_evp_pkey = evp_pkey; + ret = EOK; + +done: + rs256_pk_free(&public_key); + + return ret; +} + +int +eddsa_pubkey_to_evp_pkey(TALLOC_CTX *mem_ctx, const void *eddsa_key, + size_t eddsa_key_len, EVP_PKEY **_evp_pkey) +{ + EVP_PKEY *evp_pkey = NULL; + eddsa_pk_t *public_key = NULL; + errno_t ret; + + public_key = eddsa_pk_new(); + if (public_key == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "eddsa_pk_new failed.\n"); + ret = ENOMEM; + goto done; + } + + ret = eddsa_pk_from_ptr(public_key, eddsa_key, eddsa_key_len); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, "eddsa_pk_from_ptr failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + + evp_pkey = eddsa_pk_to_EVP_PKEY(public_key); + if (evp_pkey == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "eddsa_pk_to_EVP_PKEY failed.\n"); + ret = ENOMEM; + goto done; + } + + *_evp_pkey = evp_pkey; + ret = EOK; + +done: + eddsa_pk_free(&public_key); + + return ret; +} + +errno_t +evp_pkey_to_es256_pubkey(const EVP_PKEY *evp_pkey, struct pk_data_t *_pk_data) +{ + void *public_key = NULL; + errno_t ret; + + public_key = es256_pk_new(); + if (public_key == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "es256_pk_new failed.\n"); + ret = ENOMEM; + goto done; + } + + ret = es256_pk_from_EVP_PKEY(public_key, evp_pkey); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, + "es256_pk_from_EVP_PKEY failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + + _pk_data->public_key = public_key; + ret = EOK; + +done: + return ret; +} + +errno_t +evp_pkey_to_rs256_pubkey(const EVP_PKEY *evp_pkey, struct pk_data_t *_pk_data) +{ + void *public_key = NULL; + errno_t ret; + + public_key = rs256_pk_new(); + if (public_key == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "rs256_pk_new failed.\n"); + ret = ENOMEM; + goto done; + } + + ret = rs256_pk_from_EVP_PKEY(public_key, evp_pkey); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, + "rs256_pk_from_EVP_PKEY failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + + _pk_data->public_key = public_key; + ret = EOK; + +done: + return ret; +} + +errno_t +evp_pkey_to_eddsa_pubkey(const EVP_PKEY *evp_pkey, struct pk_data_t *_pk_data) +{ + void *public_key = NULL; + errno_t ret; + + public_key = eddsa_pk_new(); + if (public_key == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "eddsa_pk_new failed.\n"); + ret = ENOMEM; + goto done; + } + + ret = eddsa_pk_from_EVP_PKEY(public_key, evp_pkey); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, + "eddsa_pk_from_EVP_PKEY failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + + _pk_data->public_key = public_key; + ret = EOK; + +done: + return ret; +} diff --git a/src/passkey_child/passkey_child_devices.c b/src/passkey_child/passkey_child_devices.c new file mode 100644 index 0000000..2011b12 --- /dev/null +++ b/src/passkey_child/passkey_child_devices.c @@ -0,0 +1,246 @@ +/* + SSSD + + Helper child to commmunicate with passkey devices + + Authors: + Iker Pedrosa <ipedrosa@redhat.com> + + Copyright (C) 2022 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 "util/debug.h" +#include "util/util.h" + +#include "passkey_child.h" + +errno_t +list_devices(fido_dev_info_t *dev_list, size_t *dev_number) +{ + errno_t ret; + + for (int i = 0; i < TIMEOUT; i += FREQUENCY) { + ret = fido_dev_info_manifest(dev_list, DEVLIST_SIZE, dev_number); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, + "Unable to discover device(s) [%d]: %s.\n", + ret, fido_strerr(ret)); + } + + if ((*dev_number) != 0) { + DEBUG(SSSDBG_TRACE_FUNC, "Device found.\n"); + break; + } + + if (i < (TIMEOUT - 1)) { + DEBUG(SSSDBG_TRACE_FUNC, "No device available, retrying.\n"); + sleep(FREQUENCY); + } + } + + return ret; +} + +errno_t +select_device(enum action_opt action, fido_dev_info_t *dev_list, + size_t dev_list_len, fido_assert_t *assert, + fido_dev_t **_dev) +{ + fido_dev_t *dev = NULL; + const char *path; + const fido_dev_info_t *di = NULL; + errno_t ret; + + if (dev_list_len == 0) { + DEBUG(SSSDBG_OP_FAILURE, "No device found. Aborting.\n"); + ret = ENOENT; + goto done; + } else if (action == ACTION_REGISTER && dev_list_len == 1) { + dev = fido_dev_new(); + if (dev == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "fido_dev_new failed.\n"); + ret = ENOMEM; + goto done; + } + + di = fido_dev_info_ptr(dev_list, 0); + if (di == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "fido_dev_info_ptr failed.\n"); + ret = ENOMEM; + goto done; + } + + path = fido_dev_info_path(di); + if (path == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "fido_dev_info_path failed.\n"); + ret = ENOMEM; + goto done; + } + + ret = fido_dev_open(dev, path); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, "fido_dev_open failed [%d]: %s.\n", + ret, fido_strerr(ret)); + goto done; + } + + *_dev = dev; + } else if (action == ACTION_REGISTER && dev_list_len > 1) { + DEBUG(SSSDBG_OP_FAILURE, + "Only one device is supported at a time. Aborting.\n"); + fprintf(stderr, "Only one device is supported at a time. Aborting.\n"); + ret = EPERM; + goto done; + } else { + if (assert == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "assert cannot be NULL.\n"); + ret = EINVAL; + goto done; + } + + ret = select_from_multiple_devices(dev_list, dev_list_len, assert, _dev); + if (ret != FIDO_OK) { + goto done; + } + } + + ret = EOK; + +done: + if (ret != EOK) { + if (dev != NULL) { + fido_dev_close(dev); + } + fido_dev_free(&dev); + } + + return ret; +} + +errno_t +select_from_multiple_devices(fido_dev_info_t *dev_list, + size_t dev_list_len, + fido_assert_t *assert, + fido_dev_t **_dev) +{ + fido_dev_t *dev = NULL; + const fido_dev_info_t *di = NULL; + const char *path; + bool is_fido2; + errno_t ret; + + DEBUG(SSSDBG_TRACE_FUNC, + "Working with %ld authenticator(s).\n", dev_list_len); + + for (size_t i = 0; i < dev_list_len; i++) { + dev = fido_dev_new(); + if (dev == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "fido_dev_new failed.\n"); + ret = ENOMEM; + goto done; + } + + di = fido_dev_info_ptr(dev_list, i); + if (di == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "fido_dev_info_ptr failed.\n"); + ret = ENOMEM; + goto done; + } + + path = fido_dev_info_path(di); + if (path == NULL) { + DEBUG(SSSDBG_OP_FAILURE, "fido_dev_info_path failed.\n"); + ret = ENOMEM; + goto done; + } + + ret = fido_dev_open(dev, path); + if (ret != FIDO_OK) { + DEBUG(SSSDBG_OP_FAILURE, "fido_dev_open failed [%d]: %s.\n", + ret, fido_strerr(ret)); + } + + is_fido2 = fido_dev_is_fido2(dev); + ret = fido_dev_get_assert(dev, assert, NULL); + if ((is_fido2 == false && ret == FIDO_ERR_USER_PRESENCE_REQUIRED) + || (is_fido2 == true && ret == FIDO_OK)) { + *_dev = dev; + DEBUG(SSSDBG_FUNC_DATA, "Assertion found in passkey %ld.\n", i); + ret = EOK; + goto done; + } + + DEBUG(SSSDBG_FUNC_DATA, "Assertion not found in passkey %ld.\n", i); + + fido_dev_close(dev); + fido_dev_free(&dev); + } + + ret = FIDO_ERR_NOTFOUND; + DEBUG(SSSDBG_OP_FAILURE, "Assertion not found.\n"); + +done: + return ret; +} + +errno_t +get_device_options(fido_dev_t *dev, struct passkey_data *_data) +{ + bool has_pin; + bool has_uv; + bool supports_uv; + errno_t ret; + + has_uv = fido_dev_has_uv(dev); + has_pin = fido_dev_has_pin(dev); + supports_uv = fido_dev_supports_uv(dev); + + if (_data->user_verification == FIDO_OPT_TRUE && has_pin != true + && has_uv != true) { + DEBUG(SSSDBG_OP_FAILURE, + "Policy enabled user-verification but there isn't any " + "verification method set.\n"); + ret = EINVAL; + goto done; + } + + if (_data->user_verification == FIDO_OPT_OMIT + && (has_uv == true || has_pin == true)) { + DEBUG(SSSDBG_CONF_SETTINGS, + "Policy didn't indicate any preference for user-verification " + "but the key settings are enforcing it. Thus, enforcing the " + "user-verification.\n"); + _data->user_verification = FIDO_OPT_TRUE; + ret = EOK; + goto done; + } + + if (_data->user_verification == FIDO_OPT_FALSE + && supports_uv == false) { + DEBUG(SSSDBG_CONF_SETTINGS, + "Policy disabled user-verification but the key doesn't support " + "it. Thus, omitting the user-verification.\n"); + _data->user_verification = FIDO_OPT_OMIT; + ret = EOK; + goto done; + } + + ret = EOK; + +done: + + return ret; +} |