From: Ondrej Kozina Date: Thu, 20 May 2021 13:37:08 +0000 (+0200) Subject: Add support for systemd-pkcs11 libcryptsetup plugin. X-Git-Tag: v250-rc1~804^2 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=8186022c9dace11f3e21dde9982c492054c5105c;p=thirdparty%2Fsystemd.git Add support for systemd-pkcs11 libcryptsetup plugin. Add support for systemd-pkcs11 based LUKS2 device activation via libcryptsetup plugin. This make the feature (pkcs11 sealed LUKS2 keyslot passphrase) usable from both systemd utilities and cryptsetup cli. The feature is configured via -Dlibcryptsetup-plugins combo with default value set to 'auto'. It get's enabled automatically when cryptsetup 2.4.0 or later is installed in build system. --- diff --git a/meson.build b/meson.build index 22e7ac1237a..f7b18c6b98e 100644 --- a/meson.build +++ b/meson.build @@ -1801,6 +1801,20 @@ if conf.get('HAVE_LIBCRYPTSETUP_PLUGINS') == 1 install : true, install_dir : libcryptsetup_plugins_dir) endif + + if conf.get('HAVE_P11KIT') == 1 + cryptsetup_token_systemd_pkcs11 = shared_library( + 'cryptsetup-token-systemd-pkcs11', + link_args : ['-shared', + '-Wl,--version-script=' + cryptsetup_token_sym_path], + dependencies : libshared_deps + [libcryptsetup, versiondep], + link_with : [libshared], + link_whole : [cryptsetup_token_systemd_pkcs11_static], + link_depends : cryptsetup_token_sym, + install_rpath : rootlibexecdir, + install : true, + install_dir : libcryptsetup_plugins_dir) + endif endif ############################################################ diff --git a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-pkcs11.c b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-pkcs11.c new file mode 100644 index 00000000000..8e26f4f0d0e --- /dev/null +++ b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-pkcs11.c @@ -0,0 +1,143 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include + +#include "cryptsetup-token.h" +#include "cryptsetup-token-util.h" +#include "hexdecoct.h" +#include "json.h" +#include "luks2-pkcs11.h" +#include "memory-util.h" +#include "pkcs11-util.h" +#include "version.h" + +#define TOKEN_NAME "systemd-pkcs11" +#define TOKEN_VERSION_MAJOR "1" +#define TOKEN_VERSION_MINOR "0" + +/* for libcryptsetup debug purpose */ +_public_ const char *cryptsetup_token_version(void) { + return TOKEN_VERSION_MAJOR "." TOKEN_VERSION_MINOR " systemd-v" STRINGIFY(PROJECT_VERSION) " (" GIT_VERSION ")"; +} + +_public_ int cryptsetup_token_open_pin( + struct crypt_device *cd, /* is always LUKS2 context */ + int token /* is always >= 0 */, + const char *pin, + size_t pin_size, + char **password, /* freed by cryptsetup_token_buffer_free */ + size_t *password_len, + void *usrptr /* plugin defined parameter passed to crypt_activate_by_token*() API */) { + + const char *json; + int r; + + assert(!pin || pin_size); + assert(token >= 0); + + /* This must not fail at this moment (internal error) */ + r = crypt_token_json_get(cd, token, &json); + assert(token == r); + assert(json); + + return acquire_luks2_key(cd, json, usrptr, pin, pin_size, password, password_len); +} + +/* + * This function is called from within following libcryptsetup calls + * provided conditions further below are met: + * + * crypt_activate_by_token(), crypt_activate_by_token_type(type == 'systemd-pkcs11'): + * + * - token is assigned to at least one luks2 keyslot eligible to activate LUKS2 device + * (alternatively: name is set to null, flags contains CRYPT_ACTIVATE_ALLOW_UNBOUND_KEY + * and token is assigned to at least single keyslot). + * + * - if plugin defines validate funtion (see cryptsetup_token_validate below) it must have + * passed the check (aka return 0) + */ +_public_ int cryptsetup_token_open( + struct crypt_device *cd, /* is always LUKS2 context */ + int token /* is always >= 0 */, + char **password, /* freed by cryptsetup_token_buffer_free */ + size_t *password_len, + void *usrptr /* plugin defined parameter passed to crypt_activate_by_token*() API */) { + + return cryptsetup_token_open_pin(cd, token, NULL, 0, password, password_len, usrptr); +} + +/* + * libcryptsetup callback for memory deallocation of 'password' parameter passed in + * any crypt_token_open_* plugin function + */ +_public_ void cryptsetup_token_buffer_free(void *buffer, size_t buffer_len) { + erase_and_free(buffer); +} + +/* + * prints systemd-pkcs11 token content in crypt_dump(). + * 'type' and 'keyslots' fields are printed by libcryptsetup + */ +_public_ void cryptsetup_token_dump( + struct crypt_device *cd /* is always LUKS2 context */, + const char *json /* validated 'systemd-pkcs11' token if cryptsetup_token_validate is defined */) { + + int r; + size_t pkcs11_key_size; + _cleanup_free_ char *pkcs11_uri = NULL, *key_str = NULL; + _cleanup_free_ void *pkcs11_key = NULL; + + r = parse_luks2_pkcs11_data(cd, json, &pkcs11_uri, &pkcs11_key, &pkcs11_key_size); + if (r < 0) + return (void) crypt_log_debug_errno(cd, r, "Failed to parse " TOKEN_NAME " metadata: %m."); + + r = crypt_dump_buffer_to_hex_string(pkcs11_key, pkcs11_key_size, &key_str); + if (r < 0) + return (void) crypt_log_debug_errno(cd, r, "Can not dump " TOKEN_NAME " content: %m"); + + crypt_log(cd, "\tpkcs11-uri: %s\n", pkcs11_uri); + crypt_log(cd, "\tpkcs11-key: %s\n", key_str); +} + +/* + * Note: + * If plugin is available in library path, it's called in before following libcryptsetup calls: + * + * crypt_token_json_set, crypt_dump, any crypt_activate_by_token_* flavour + */ +_public_ int cryptsetup_token_validate( + struct crypt_device *cd, /* is always LUKS2 context */ + const char *json /* contains valid 'type' and 'keyslots' fields. 'type' is 'systemd-pkcs11' */) { + + int r; + JsonVariant *w; + _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; + + r = json_parse(json, 0, &v, NULL, NULL); + if (r < 0) + return crypt_log_debug_errno(cd, r, "Could not parse " TOKEN_NAME " json object: %m."); + + w = json_variant_by_key(v, "pkcs11-uri"); + if (!w || !json_variant_is_string(w)) { + crypt_log_debug(cd, "PKCS#11 token data lacks 'pkcs11-uri' field."); + return 1; + } + + if (!pkcs11_uri_valid(json_variant_string(w))) { + crypt_log_debug(cd, "PKCS#11 token data contains invalid PKCS#11 URI."); + return 1; + } + + w = json_variant_by_key(v, "pkcs11-key"); + if (!w || !json_variant_is_string(w)) { + crypt_log_debug(cd, "PKCS#11 token data lacks 'pkcs11-key' field."); + return 1; + } + + r = unbase64mem(json_variant_string(w), SIZE_MAX, NULL, NULL); + if (r < 0) + return crypt_log_debug_errno(cd, r, "Failed to decode base64 encoded key: %m."); + + return 0; +} diff --git a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-util.h b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-util.h index 124bfab63b2..57ffca136f4 100644 --- a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-util.h +++ b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-util.h @@ -4,13 +4,15 @@ #include #include +#include /* crypt_dump() internal indentation magic */ #define CRYPT_DUMP_LINE_SEP "\n\t " -#define crypt_log_debug(cd, ...) crypt_logf(cd, CRYPT_LOG_DEBUG, __VA_ARGS__) -#define crypt_log_error(cd, ...) crypt_logf(cd, CRYPT_LOG_ERROR, __VA_ARGS__) -#define crypt_log(cd, ...) crypt_logf(cd, CRYPT_LOG_NORMAL, __VA_ARGS__) +#define crypt_log_debug(cd, ...) crypt_logf(cd, CRYPT_LOG_DEBUG, __VA_ARGS__) +#define crypt_log_error(cd, ...) crypt_logf(cd, CRYPT_LOG_ERROR, __VA_ARGS__) +#define crypt_log_verbose(cd, ...) crypt_logf(cd, CRYPT_LOG_VERBOSE, __VA_ARGS__) +#define crypt_log(cd, ...) crypt_logf(cd, CRYPT_LOG_NORMAL, __VA_ARGS__) #define crypt_log_full_errno(cd, e, lvl, ...) ({ \ int _e = abs(e), _s = errno; \ diff --git a/src/cryptsetup/cryptsetup-tokens/luks2-pkcs11.c b/src/cryptsetup/cryptsetup-tokens/luks2-pkcs11.c new file mode 100644 index 00000000000..9e88c8cfc3a --- /dev/null +++ b/src/cryptsetup/cryptsetup-tokens/luks2-pkcs11.c @@ -0,0 +1,271 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include + +#include "cryptsetup-token-util.h" +#include "escape.h" +#include "hexdecoct.h" +#include "json.h" +#include "luks2-pkcs11.h" +#include "memory-util.h" +#include "pkcs11-util.h" +#include "time-util.h" + +struct luks2_pkcs11_callback_data { + struct crypt_device *cd; + const char *pin; + size_t pin_size; + void *encrypted_key; + size_t encrypted_key_size; + void *decrypted_key; + size_t decrypted_key_size; +}; + +static int luks2_pkcs11_callback( + CK_FUNCTION_LIST *m, + CK_SESSION_HANDLE session, + CK_SLOT_ID slot_id, + const CK_SLOT_INFO *slot_info, + const CK_TOKEN_INFO *token_info, + P11KitUri *uri, + void *userdata) { + + CK_OBJECT_HANDLE object; + CK_RV rv; + CK_TOKEN_INFO updated_token_info; + int r; + _cleanup_free_ char *token_label = NULL; + struct luks2_pkcs11_callback_data *data = userdata; + + assert(m); + assert(slot_info); + assert(token_info); + assert(uri); + assert(data); + + token_label = pkcs11_token_label(token_info); + if (!token_label) + return -ENOMEM; + + /* Called for every token matching our URI */ + r = pkcs11_token_login_by_pin(m, session, token_info, token_label, data->pin, data->pin_size); + if (r == -ENOLCK) { + /* Referesh the token info, so that we can prompt knowing the new flags if they changed. */ + rv = m->C_GetTokenInfo(slot_id, &updated_token_info); + if (rv != CKR_OK) { + crypt_log_error(data->cd, + "Failed to acquire updated security token information for slot %lu: %s", + slot_id, p11_kit_strerror(rv)); + return -EIO; + } + token_info = &updated_token_info; + r = -ENOANO; + } + + if (r == -ENOANO) { + if (FLAGS_SET(token_info->flags, CKF_USER_PIN_FINAL_TRY)) + crypt_log_error(data->cd, "Please enter correct PIN for security token " + "'%s' in order to unlock it (final try).", token_label); + else if (FLAGS_SET(token_info->flags, CKF_USER_PIN_COUNT_LOW)) + crypt_log_error(data->cd, "PIN has been entered incorrectly previously, " + "please enter correct PIN for security token '%s' in order to unlock it.", + token_label); + } + + if (r == -EPERM) /* pin is locked, but map it to -ENOANO anyway */ + r = -ENOANO; + + if (r < 0) + return r; + + r = pkcs11_token_find_private_key(m, session, uri, &object); + if (r < 0) + return r; + + r = pkcs11_token_decrypt_data( + m, + session, + object, + data->encrypted_key, + data->encrypted_key_size, + &data->decrypted_key, + &data->decrypted_key_size); + if (r < 0) + return r; + + return 0; +} + +static void luks2_pkcs11_callback_data_release(struct luks2_pkcs11_callback_data *data) { + erase_and_free(data->decrypted_key); +} + +static int acquire_luks2_key_by_pin( + struct crypt_device *cd, + const char *pkcs11_uri, + const void *pin, + size_t pin_size, + void *encrypted_key, + size_t encrypted_key_size, + void **ret_decrypted_key, + size_t *ret_decrypted_key_size) { + + int r; + _cleanup_(luks2_pkcs11_callback_data_release) struct luks2_pkcs11_callback_data data = { + .cd = cd, + .pin = pin, + .pin_size = pin_size, + .encrypted_key = encrypted_key, + .encrypted_key_size = encrypted_key_size, + }; + + assert(pkcs11_uri); + assert(encrypted_key); + assert(ret_decrypted_key); + assert(ret_decrypted_key_size); + + r = pkcs11_find_token(pkcs11_uri, luks2_pkcs11_callback, &data); + if (r < 0) + return r; + + *ret_decrypted_key = TAKE_PTR(data.decrypted_key); + *ret_decrypted_key_size = data.decrypted_key_size; + + return 0; +} + +/* called from within systemd utilities */ +static int acquire_luks2_key_systemd( + const char *pkcs11_uri, + systemd_pkcs11_plugin_params *params, + void *encrypted_key, + size_t encrypted_key_size, + void **ret_decrypted_key, + size_t *ret_decrypted_key_size) { + + int r; + _cleanup_(pkcs11_crypt_device_callback_data_release) pkcs11_crypt_device_callback_data data = { + .encrypted_key = encrypted_key, + .encrypted_key_size = encrypted_key_size, + .free_encrypted_key = false + }; + + assert(pkcs11_uri); + assert(encrypted_key); + assert(ret_decrypted_key); + assert(ret_decrypted_key_size); + assert(params); + + data.friendly_name = params->friendly_name; + data.headless = params->headless; + data.until = params->until; + + /* The functions called here log about all errors, except for EAGAIN which means "token not found right now" */ + r = pkcs11_find_token(pkcs11_uri, pkcs11_crypt_device_callback, &data); + if (r < 0) + return r; + + *ret_decrypted_key = TAKE_PTR(data.decrypted_key); + *ret_decrypted_key_size = data.decrypted_key_size; + + return 0; +} + +int acquire_luks2_key( + struct crypt_device *cd, + const char *json, + void *userdata, + const void *pin, + size_t pin_size, + char **ret_password, + size_t *ret_password_size) { + + int r; + size_t decrypted_key_size, encrypted_key_size; + _cleanup_(erase_and_freep) void *decrypted_key = NULL; + _cleanup_(erase_and_freep) char *base64_encoded = NULL; + _cleanup_free_ char *pkcs11_uri = NULL; + _cleanup_free_ void *encrypted_key = NULL; + systemd_pkcs11_plugin_params *pkcs11_params = userdata; + + assert(json); + assert(ret_password); + assert(ret_password_size); + + r = parse_luks2_pkcs11_data(cd, json, &pkcs11_uri, &encrypted_key, &encrypted_key_size); + if (r < 0) + return r; + + if (pkcs11_params && pin) + crypt_log_verbose(cd, "PIN parameter ignored in interactive mode."); + + if (pkcs11_params) /* systemd based activation with interactive pin query callbacks */ + r = acquire_luks2_key_systemd( + pkcs11_uri, + pkcs11_params, + encrypted_key, encrypted_key_size, + &decrypted_key, &decrypted_key_size); + else /* default activation that provides single PIN if needed */ + r = acquire_luks2_key_by_pin( + cd, pkcs11_uri, pin, pin_size, + encrypted_key, encrypted_key_size, + &decrypted_key, &decrypted_key_size); + if (r < 0) + return r; + + r = base64mem(decrypted_key, decrypted_key_size, &base64_encoded); + if (r < 0) + return crypt_log_error_errno(cd, r, "Can not base64 encode key: %m"); + + *ret_password = TAKE_PTR(base64_encoded); + *ret_password_size = strlen(*ret_password); + + return 0; +} + +int parse_luks2_pkcs11_data( + struct crypt_device *cd, + const char *json, + char **ret_uri, + void **ret_encrypted_key, + size_t *ret_encrypted_key_size) { + + int r; + size_t key_size; + _cleanup_free_ char *uri = NULL; + _cleanup_free_ void *key = NULL; + _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; + JsonVariant *w; + + assert(json); + assert(ret_uri); + assert(ret_encrypted_key); + assert(ret_encrypted_key_size); + + r = json_parse(json, 0, &v, NULL, NULL); + if (r < 0) + return r; + + w = json_variant_by_key(v, "pkcs11-uri"); + if (!w) + return -EINVAL; + + uri = strdup(json_variant_string(w)); + if (!uri) + return -ENOMEM; + + w = json_variant_by_key(v, "pkcs11-key"); + if (!w) + return -EINVAL; + + r = unbase64mem(json_variant_string(w), SIZE_MAX, &key, &key_size); + if (r < 0) + return crypt_log_debug_errno(cd, r, "Failed to decode base64 encoded key: %m."); + + *ret_uri = TAKE_PTR(uri); + *ret_encrypted_key = TAKE_PTR(key); + *ret_encrypted_key_size = key_size; + + return 0; +} diff --git a/src/cryptsetup/cryptsetup-tokens/luks2-pkcs11.h b/src/cryptsetup/cryptsetup-tokens/luks2-pkcs11.h new file mode 100644 index 00000000000..41ce9f0608d --- /dev/null +++ b/src/cryptsetup/cryptsetup-tokens/luks2-pkcs11.h @@ -0,0 +1,21 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#pragma once + +struct crypt_device; + +int acquire_luks2_key( + struct crypt_device *cd, + const char *json, + void *userdata, + const void *pin, + size_t pin_size, + char **password, + size_t *password_size); + +int parse_luks2_pkcs11_data( + struct crypt_device *cd, + const char *json, + char **ret_uri, + void **ret_encrypted_key, + size_t *ret_encrypted_key_size); diff --git a/src/cryptsetup/cryptsetup-tokens/meson.build b/src/cryptsetup/cryptsetup-tokens/meson.build index 569fafb89f4..d174fcb714e 100644 --- a/src/cryptsetup/cryptsetup-tokens/meson.build +++ b/src/cryptsetup/cryptsetup-tokens/meson.build @@ -43,4 +43,22 @@ if conf.get('HAVE_LIBFIDO2') == 1 c_args : cryptsetup_token_c_args) endif +if conf.get('HAVE_P11KIT') == 1 + cryptsetup_token_systemd_pkcs11_sources = files(''' + cryptsetup-token-systemd-pkcs11.c + cryptsetup-token.h + cryptsetup-token-util.h + cryptsetup-token-util.c + luks2-pkcs11.c + luks2-pkcs11.h + '''.split()) + + cryptsetup_token_systemd_pkcs11_static = static_library( + 'cryptsetup-token-systemd-pkcs11_static', + cryptsetup_token_systemd_pkcs11_sources, + include_directories : includes, + dependencies : libshared_deps + [libcryptsetup, versiondep], + c_args : cryptsetup_token_c_args) +endif + endif diff --git a/src/cryptsetup/cryptsetup.c b/src/cryptsetup/cryptsetup.c index 4615171b745..fdd57def868 100644 --- a/src/cryptsetup/cryptsetup.c +++ b/src/cryptsetup/cryptsetup.c @@ -996,6 +996,32 @@ static int attach_luks_or_plain_or_bitlk_by_fido2( return 0; } +static int attach_luks2_by_pkcs11( + struct crypt_device *cd, + const char *name, + const char *friendly_name, + usec_t until, + bool headless, + uint32_t flags) { + + int r = -ENOTSUP; +#if HAVE_LIBCRYPTSETUP_PLUGINS + if (!crypt_get_type(cd) || strcmp(crypt_get_type(cd), CRYPT_LUKS2)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Automatic PKCS#11 metadata requires LUKS2 device."); + + systemd_pkcs11_plugin_params params = { + .friendly_name = friendly_name, + .until = until, + .headless = headless + }; + + r = crypt_activate_by_token_pin(cd, name, "systemd-pkcs11", CRYPT_ANY_TOKEN, NULL, 0, ¶ms, flags); + if (r > 0) /* returns unlocked keyslot id on success */ + r = 0; +#endif + return r; +} + static int attach_luks_or_plain_or_bitlk_by_pkcs11( struct crypt_device *cd, const char *name, @@ -1013,23 +1039,26 @@ static int attach_luks_or_plain_or_bitlk_by_pkcs11( _cleanup_(sd_event_unrefp) sd_event *event = NULL; _cleanup_free_ void *discovered_key = NULL; int keyslot = arg_key_slot, r; - const char *uri; + const char *uri = NULL; + bool use_libcryptsetup_plugin = libcryptsetup_plugins_support(); assert(cd); assert(name); assert(arg_pkcs11_uri || arg_pkcs11_uri_auto); if (arg_pkcs11_uri_auto) { - r = find_pkcs11_auto_data(cd, &discovered_uri, &discovered_key, &discovered_key_size, &keyslot); - if (IN_SET(r, -ENOTUNIQ, -ENXIO)) - return log_debug_errno(SYNTHETIC_ERRNO(EAGAIN), - "Automatic PKCS#11 metadata discovery was not possible because missing or not unique, falling back to traditional unlocking."); - if (r < 0) - return r; + if (!use_libcryptsetup_plugin) { + r = find_pkcs11_auto_data(cd, &discovered_uri, &discovered_key, &discovered_key_size, &keyslot); + if (IN_SET(r, -ENOTUNIQ, -ENXIO)) + return log_debug_errno(SYNTHETIC_ERRNO(EAGAIN), + "Automatic PKCS#11 metadata discovery was not possible because missing or not unique, falling back to traditional unlocking."); + if (r < 0) + return r; - uri = discovered_uri; - key_data = discovered_key; - key_data_size = discovered_key_size; + uri = discovered_uri; + key_data = discovered_key; + key_data_size = discovered_key_size; + } } else { uri = arg_pkcs11_uri; @@ -1044,17 +1073,22 @@ static int attach_luks_or_plain_or_bitlk_by_pkcs11( for (;;) { bool processed = false; - r = decrypt_pkcs11_key( - name, - friendly, - uri, - key_file, arg_keyfile_size, arg_keyfile_offset, - key_data, key_data_size, - until, - arg_headless, - &decrypted_key, &decrypted_key_size); - if (r >= 0) - break; + if (use_libcryptsetup_plugin && arg_pkcs11_uri_auto) + r = attach_luks2_by_pkcs11(cd, name, friendly, until, arg_headless, flags); + else { + r = decrypt_pkcs11_key( + name, + friendly, + uri, + key_file, arg_keyfile_size, arg_keyfile_offset, + key_data, key_data_size, + until, + arg_headless, + &decrypted_key, &decrypted_key_size); + if (r >= 0) + break; + } + if (r != -EAGAIN) /* EAGAIN means: token not found */ return r; @@ -1094,6 +1128,7 @@ static int attach_luks_or_plain_or_bitlk_by_pkcs11( log_debug("Got one or more potentially relevant udev events, rescanning PKCS#11..."); } + assert(decrypted_key); if (pass_volume_key) r = crypt_activate_by_volume_key(cd, name, decrypted_key, decrypted_key_size, flags); diff --git a/src/shared/pkcs11-util.h b/src/shared/pkcs11-util.h index afacec55bfb..f8195d7a369 100644 --- a/src/shared/pkcs11-util.h +++ b/src/shared/pkcs11-util.h @@ -74,5 +74,11 @@ int pkcs11_crypt_device_callback( #endif +typedef struct { + const char *friendly_name; + usec_t until; + bool headless; +} systemd_pkcs11_plugin_params; + int pkcs11_list_tokens(void); int pkcs11_find_token_auto(char **ret);