diff --git a/meson.build b/meson.build index f4a948cd65..f7b18c6b98 100644 --- a/meson.build +++ b/meson.build @@ -1787,6 +1787,34 @@ if conf.get('HAVE_LIBCRYPTSETUP_PLUGINS') == 1 install : true, install_dir : libcryptsetup_plugins_dir) endif + + if conf.get('HAVE_LIBFIDO2') == 1 + cryptsetup_token_systemd_fido2 = shared_library( + 'cryptsetup-token-systemd-fido2', + link_args : ['-shared', + '-Wl,--version-script=' + cryptsetup_token_sym_path], + dependencies : libshared_deps + [libcryptsetup, versiondep], + link_with : [libshared], + link_whole : [cryptsetup_token_systemd_fido2_static], + link_depends : cryptsetup_token_sym, + install_rpath : rootlibexecdir, + 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-pkcs11.c b/src/cryptsetup/cryptsetup-pkcs11.c index e743f10151..31960de597 100644 --- a/src/cryptsetup/cryptsetup-pkcs11.c +++ b/src/cryptsetup/cryptsetup-pkcs11.c @@ -24,83 +24,6 @@ #include "stat-util.h" #include "strv.h" -struct pkcs11_callback_data { - const char *friendly_name; - usec_t until; - void *encrypted_key; - size_t encrypted_key_size; - void *decrypted_key; - size_t decrypted_key_size; - bool free_encrypted_key; - bool headless; -}; - -static void pkcs11_callback_data_release(struct pkcs11_callback_data *data) { - erase_and_free(data->decrypted_key); - - if (data->free_encrypted_key) - free(data->encrypted_key); -} - -static int 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) { - - struct pkcs11_callback_data *data = userdata; - CK_OBJECT_HANDLE object; - int r; - - assert(m); - assert(slot_info); - assert(token_info); - assert(uri); - assert(data); - - /* Called for every token matching our URI */ - - r = pkcs11_token_login( - m, - session, - slot_id, - token_info, - data->friendly_name, - "drive-harddisk", - "pkcs11-pin", - "cryptsetup.pkcs11-pin", - data->until, - data->headless, - NULL); - if (r < 0) - return r; - - /* We are likely called during early boot, where entropy is scarce. Mix some data from the PKCS#11 - * token, if it supports that. It should be cheap, given that we already are talking to it anyway and - * shouldn't hurt. */ - (void) pkcs11_token_acquire_rng(m, session); - - 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; -} - int decrypt_pkcs11_key( const char *volume_name, const char *friendly_name, @@ -115,7 +38,7 @@ int decrypt_pkcs11_key( void **ret_decrypted_key, size_t *ret_decrypted_key_size) { - _cleanup_(pkcs11_callback_data_release) struct pkcs11_callback_data data = { + _cleanup_(pkcs11_crypt_device_callback_data_release) pkcs11_crypt_device_callback_data data = { .friendly_name = friendly_name, .until = until, .headless = headless, @@ -155,7 +78,7 @@ int decrypt_pkcs11_key( data.free_encrypted_key = true; } - r = pkcs11_find_token(pkcs11_uri, pkcs11_callback, &data); + r = pkcs11_find_token(pkcs11_uri, pkcs11_crypt_device_callback, &data); if (r < 0) return r; diff --git a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-fido2.c b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-fido2.c new file mode 100644 index 0000000000..12bb976b21 --- /dev/null +++ b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-fido2.c @@ -0,0 +1,224 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include +#include + +#include "cryptsetup-token.h" +#include "cryptsetup-token-util.h" +#include "hexdecoct.h" +#include "json.h" +#include "luks2-fido2.h" +#include "memory-util.h" +#include "version.h" + +#define TOKEN_NAME "systemd-fido2" +#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 */) { + + int r; + const char *json; + _cleanup_(erase_and_freep) char *pin_string = NULL; + + 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); + + if (pin && memchr(pin, 0, pin_size - 1)) + return crypt_log_error_errno(cd, ENOANO, "PIN must be characters string."); + + /* pin was passed as pin = pin, pin_size = strlen(pin). We need to add terminating + * NULL byte to addressable memory*/ + if (pin && pin[pin_size-1] != '\0') { + pin_string = strndup(pin, pin_size); + if (!pin_string) + return crypt_log_oom(cd); + } + + return acquire_luks2_key(cd, json, (const char *)usrptr, pin_string ?: pin, 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-fido2'): + * + * - 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-fido2 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-tpm2' token if cryptsetup_token_validate is defined */) { + + int r; + Fido2EnrollFlags required; + size_t cid_size, salt_size; + const char *client_pin_req_str, *up_req_str, *uv_req_str; + _cleanup_free_ void *cid = NULL, *salt = NULL; + _cleanup_free_ char *rp_id = NULL, *cid_str = NULL, *salt_str = NULL; + + assert(json); + + r = parse_luks2_fido2_data(cd, json, &rp_id, &salt, &salt_size, &cid, &cid_size, &required); + 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(cid, cid_size, &cid_str); + if (r < 0) + return (void) crypt_log_debug_errno(cd, r, "Can not dump " TOKEN_NAME " content: %m"); + + r = crypt_dump_buffer_to_hex_string(salt, salt_size, &salt_str); + if (r < 0) + return (void) crypt_log_debug_errno(cd, r, "Can not dump " TOKEN_NAME " content: %m"); + + if (required & FIDO2ENROLL_PIN) + client_pin_req_str = "true"; + else if (required & FIDO2ENROLL_PIN_IF_NEEDED) + client_pin_req_str = NULL; + else + client_pin_req_str = "false"; + + if (required & FIDO2ENROLL_UP) + up_req_str = "true"; + else if (required & FIDO2ENROLL_UP_IF_NEEDED) + up_req_str = NULL; + else + up_req_str = "false"; + + if (required & FIDO2ENROLL_UV) + uv_req_str = "true"; + else if (required & FIDO2ENROLL_UV_OMIT) + uv_req_str = NULL; + else + uv_req_str = "false"; + + crypt_log(cd, "\tfido2-credential:" CRYPT_DUMP_LINE_SEP "%s\n", cid_str); + crypt_log(cd, "\tfido2-salt: %s\n", salt_str); + + /* optional fields */ + if (rp_id) + crypt_log(cd, "\tfido2-rp: %s\n", rp_id); + if (client_pin_req_str) + crypt_log(cd, "\tfido2-clientPin-required:" CRYPT_DUMP_LINE_SEP "%s\n", + client_pin_req_str); + if (up_req_str) + crypt_log(cd, "\tfido2-up-required:" CRYPT_DUMP_LINE_SEP "%s\n", up_req_str); + if (uv_req_str) + crypt_log(cd, "\tfido2-uv-required:" CRYPT_DUMP_LINE_SEP "%s\n", uv_req_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-tpm2' */) { + + int r; + JsonVariant *w; + _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; + + assert(json); + + 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, "fido2-credential"); + if (!w || !json_variant_is_string(w)) { + crypt_log_debug(cd, "FIDO2 token data lacks 'fido2-credential' field."); + return 1; + } + + r = unbase64mem(json_variant_string(w), SIZE_MAX, NULL, NULL); + if (r < 0) + return crypt_log_debug_errno(cd, r, "Invalid base64 data in 'fido2-credential' field: %m"); + + w = json_variant_by_key(v, "fido2-salt"); + if (!w || !json_variant_is_string(w)) { + crypt_log_debug(cd, "FIDO2 token data lacks 'fido2-salt' 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 salt: %m."); + + /* The "rp" field is optional. */ + w = json_variant_by_key(v, "fido2-rp"); + if (w && !json_variant_is_string(w)) { + crypt_log_debug(cd, "FIDO2 token data's 'fido2-rp' field is not a string."); + return 1; + } + + /* The "fido2-clientPin-required" field is optional. */ + w = json_variant_by_key(v, "fido2-clientPin-required"); + if (w && !json_variant_is_boolean(w)) { + crypt_log_debug(cd, "FIDO2 token data's 'fido2-clientPin-required' field is not a boolean."); + return 1; + } + + /* The "fido2-up-required" field is optional. */ + w = json_variant_by_key(v, "fido2-up-required"); + if (w && !json_variant_is_boolean(w)) { + crypt_log_debug(cd, "FIDO2 token data's 'fido2-up-required' field is not a boolean."); + return 1; + } + + /* The "fido2-uv-required" field is optional. */ + w = json_variant_by_key(v, "fido2-uv-required"); + if (w && !json_variant_is_boolean(w)) { + crypt_log_debug(cd, "FIDO2 token data's 'fido2-uv-required' field is not a boolean."); + return 1; + } + + return 0; +} 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 0000000000..8e26f4f0d0 --- /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 b8ea4c2422..57ffca136f 100644 --- a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-util.h +++ b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-util.h @@ -2,23 +2,34 @@ #pragma once +#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_debug_errno(cd, e, ...) ({ \ +#define crypt_log_full_errno(cd, e, lvl, ...) ({ \ int _e = abs(e), _s = errno; \ errno = _e; \ - crypt_logf(cd, CRYPT_LOG_DEBUG, __VA_ARGS__); \ + crypt_logf(cd, lvl, __VA_ARGS__); \ errno = _s; \ -_e; \ }) +#define crypt_log_debug_errno(cd, e, ...) \ + crypt_log_full_errno(cd, e, CRYPT_LOG_DEBUG, __VA_ARGS__) + +#define crypt_log_error_errno(cd, e, ...) \ + crypt_log_full_errno(cd, e, CRYPT_LOG_ERROR, __VA_ARGS__) + +#define crypt_log_oom(cd) crypt_log_error_errno(cd, ENOMEM, "Not enough memory.") + int crypt_dump_buffer_to_hex_string( const char *buf, size_t buf_size, diff --git a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token.h b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token.h index 010e010ff0..2a9d23f3e3 100644 --- a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token.h +++ b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token.h @@ -8,6 +8,10 @@ const char *cryptsetup_token_version(void); int cryptsetup_token_open(struct crypt_device *cd, int token, char **password, size_t *password_len, void *usrptr); +int cryptsetup_token_open_pin(struct crypt_device *cd, int token, + const char *pin, size_t pin_size, + char **password, size_t *password_len, void *usrptr); + void cryptsetup_token_dump(struct crypt_device *cd, const char *json); int cryptsetup_token_validate(struct crypt_device *cd, const char *json); diff --git a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token.sym b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token.sym index bddf1b59b2..730e78e055 100644 --- a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token.sym +++ b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token.sym @@ -10,6 +10,7 @@ CRYPTSETUP_TOKEN_1.0 { global: cryptsetup_token_open; + cryptsetup_token_open_pin; cryptsetup_token_buffer_free; cryptsetup_token_validate; cryptsetup_token_dump; diff --git a/src/cryptsetup/cryptsetup-tokens/luks2-fido2.c b/src/cryptsetup/cryptsetup-tokens/luks2-fido2.c new file mode 100644 index 0000000000..442a57b63f --- /dev/null +++ b/src/cryptsetup/cryptsetup-tokens/luks2-fido2.c @@ -0,0 +1,158 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#include "cryptsetup-token-util.h" +#include "hexdecoct.h" +#include "json.h" +#include "luks2-fido2.h" +#include "memory-util.h" +#include "strv.h" + +int acquire_luks2_key( + struct crypt_device *cd, + const char *json, + const char *device, + const char *pin, + char **ret_keyslot_passphrase, + size_t *ret_keyslot_passphrase_size) { + + int r; + Fido2EnrollFlags required; + size_t cid_size, salt_size, decrypted_key_size; + _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; + _cleanup_free_ void *cid = NULL, *salt = NULL; + _cleanup_free_ char *rp_id = NULL; + _cleanup_(erase_and_freep) void *decrypted_key = NULL; + _cleanup_(erase_and_freep) char *base64_encoded = NULL; + _cleanup_strv_free_erase_ char **pins = NULL; + + assert(ret_keyslot_passphrase); + assert(ret_keyslot_passphrase_size); + + r = parse_luks2_fido2_data(cd, json, &rp_id, &salt, &salt_size, &cid, &cid_size, &required); + if (r < 0) + return r; + + if (pin) { + pins = strv_new(pin); + if (!pins) + return crypt_log_oom(cd); + } + + /* configured to use pin but none was provided */ + if ((required & FIDO2ENROLL_PIN) && strv_isempty(pins)) + return -ENOANO; + + r = fido2_use_hmac_hash( + device, + rp_id ?: "io.systemd.cryptsetup", + salt, salt_size, + cid, cid_size, + pins, + required, + &decrypted_key, + &decrypted_key_size); + if (r == -ENOLCK) /* libcryptsetup returns -ENOANO also on wrong pin */ + r = -ENOANO; + if (r < 0) + return r; + + /* Before using this key as passphrase we base64 encode it, for compat with homed */ + 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_keyslot_passphrase = TAKE_PTR(base64_encoded); + *ret_keyslot_passphrase_size = strlen(*ret_keyslot_passphrase); + + return 0; +} + +/* this function expects valid "systemd-fido2" in json */ +int parse_luks2_fido2_data( + struct crypt_device *cd, + const char *json, + char **ret_rp_id, + void **ret_salt, + size_t *ret_salt_size, + void **ret_cid, + size_t *ret_cid_size, + Fido2EnrollFlags *ret_required) { + + _cleanup_free_ void *cid = NULL, *salt = NULL; + size_t cid_size = 0, salt_size = 0; + _cleanup_free_ char *rp = NULL; + int r; + _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; + JsonVariant *w; + Fido2EnrollFlags required = 0; + + assert(json); + assert(ret_rp_id); + assert(ret_salt); + assert(ret_salt_size); + assert(ret_cid); + assert(ret_cid_size); + assert(ret_required); + + r = json_parse(json, 0, &v, NULL, NULL); + if (r < 0) + return crypt_log_error_errno(cd, r, "Failed to parse JSON token data: %m"); + + w = json_variant_by_key(v, "fido2-credential"); + if (!w) + return -EINVAL; + + r = unbase64mem(json_variant_string(w), SIZE_MAX, &cid, &cid_size); + if (r < 0) + return crypt_log_error_errno(cd, r, "Failed to parse 'fido2-credentials' field: %m"); + + w = json_variant_by_key(v, "fido2-salt"); + if (!w) + return -EINVAL; + + r = unbase64mem(json_variant_string(w), SIZE_MAX, &salt, &salt_size); + if (r < 0) + return crypt_log_error_errno(cd, r, "Failed to parse 'fido2-salt' field: %m"); + + w = json_variant_by_key(v, "fido2-rp"); + if (w) { + /* The "rp" field is optional. */ + rp = strdup(json_variant_string(w)); + if (!rp) { + crypt_log_error(cd, "Not enough memory."); + return -ENOMEM; + } + } + + w = json_variant_by_key(v, "fido2-clientPin-required"); + if (w) + /* The "fido2-clientPin-required" field is optional. */ + SET_FLAG(required, FIDO2ENROLL_PIN, json_variant_boolean(w)); + else + required |= FIDO2ENROLL_PIN_IF_NEEDED; /* compat with 248, where the field was unset */ + + w = json_variant_by_key(v, "fido2-up-required"); + if (w) + /* The "fido2-up-required" field is optional. */ + SET_FLAG(required, FIDO2ENROLL_UP, json_variant_boolean(w)); + else + required |= FIDO2ENROLL_UP_IF_NEEDED; /* compat with 248 */ + + w = json_variant_by_key(v, "fido2-uv-required"); + if (w) + /* The "fido2-uv-required" field is optional. */ + SET_FLAG(required, FIDO2ENROLL_UV, json_variant_boolean(w)); + else + required |= FIDO2ENROLL_UV_OMIT; /* compat with 248 */ + + *ret_rp_id = TAKE_PTR(rp); + *ret_cid = TAKE_PTR(cid); + *ret_cid_size = cid_size; + *ret_salt = TAKE_PTR(salt); + *ret_salt_size = salt_size; + *ret_required = required; + + return 0; +} diff --git a/src/cryptsetup/cryptsetup-tokens/luks2-fido2.h b/src/cryptsetup/cryptsetup-tokens/luks2-fido2.h new file mode 100644 index 0000000000..48416ec481 --- /dev/null +++ b/src/cryptsetup/cryptsetup-tokens/luks2-fido2.h @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "libfido2-util.h" + +struct crypt_device; + +int acquire_luks2_key( + struct crypt_device *cd, + const char *json, + const char *device, + const char *pin, + char **ret_keyslot_passphrase, + size_t *ret_keyslot_passphrase_size); + +int parse_luks2_fido2_data( + struct crypt_device *cd, + const char *json, + char **ret_rp_id, + void **ret_salt, + size_t *ret_salt_size, + void **ret_cid, + size_t *ret_cid_size, + Fido2EnrollFlags *ret_required); diff --git a/src/cryptsetup/cryptsetup-tokens/luks2-pkcs11.c b/src/cryptsetup/cryptsetup-tokens/luks2-pkcs11.c new file mode 100644 index 0000000000..9e88c8cfc3 --- /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 0000000000..41ce9f0608 --- /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 76664296f1..d174fcb714 100644 --- a/src/cryptsetup/cryptsetup-tokens/meson.build +++ b/src/cryptsetup/cryptsetup-tokens/meson.build @@ -25,4 +25,40 @@ if conf.get('HAVE_TPM2') == 1 c_args : cryptsetup_token_c_args) endif +if conf.get('HAVE_LIBFIDO2') == 1 + cryptsetup_token_systemd_fido2_sources = files(''' + cryptsetup-token-systemd-fido2.c + cryptsetup-token.h + cryptsetup-token-util.h + cryptsetup-token-util.c + luks2-fido2.c + luks2-fido2.h + '''.split()) + + cryptsetup_token_systemd_fido2_static = static_library( + 'cryptsetup-token-systemd-fido2_static', + cryptsetup_token_systemd_fido2_sources, + include_directories : includes, + dependencies : libshared_deps + [libcryptsetup, versiondep], + 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 0d3ea9cda6..fdd57def86 100644 --- a/src/cryptsetup/cryptsetup.c +++ b/src/cryptsetup/cryptsetup.c @@ -736,6 +736,105 @@ static int make_security_device_monitor(sd_event *event, sd_device_monitor **ret return 0; } +static bool libcryptsetup_plugins_support(void) { +#if HAVE_LIBCRYPTSETUP_PLUGINS + return crypt_token_external_path() != NULL; +#else + return false; +#endif +} + +#if HAVE_LIBCRYPTSETUP_PLUGINS +static int acquire_pins_from_env_variable(char ***ret_pins) { + char *e; + _cleanup_strv_free_erase_ char **pins = NULL; + + assert(ret_pins); + + e = getenv("PIN"); + if (e) { + pins = strv_new(e); + if (!pins) + return log_oom(); + + string_erase(e); + if (unsetenv("PIN") < 0) + return log_error_errno(errno, "Failed to unset $PIN: %m"); + } + + *ret_pins = TAKE_PTR(pins); + + return 0; +} +#endif + +static int attach_luks2_by_fido2( + struct crypt_device *cd, + const char *name, + usec_t until, + bool headless, + void *usrptr, + uint32_t activation_flags) { + + int r = -EOPNOTSUPP; +#if HAVE_LIBCRYPTSETUP_PLUGINS + char **p; + _cleanup_strv_free_erase_ char **pins = NULL; + AskPasswordFlags flags = ASK_PASSWORD_PUSH_CACHE | ASK_PASSWORD_ACCEPT_CACHED; + + r = crypt_activate_by_token_pin(cd, name, "systemd-fido2", CRYPT_ANY_TOKEN, NULL, 0, usrptr, activation_flags); + if (r > 0) /* returns unlocked keyslot id on success */ + r = 0; + if (r != -ENOANO) /* needs pin or pin is wrong */ + return r; + + r = acquire_pins_from_env_variable(&pins); + if (r < 0) + return r; + + STRV_FOREACH(p, pins) { + r = crypt_activate_by_token_pin(cd, name, "systemd-fido2", CRYPT_ANY_TOKEN, *p, strlen(*p), usrptr, activation_flags); + if (r > 0) /* returns unlocked keyslot id on success */ + r = 0; + if (r != -ENOANO) /* needs pin or pin is wrong */ + return r; + } + + if (headless) + return log_error_errno(SYNTHETIC_ERRNO(ENOPKG), "PIN querying disabled via 'headless' option. Use the '$PIN' environment variable."); + + pins = strv_free_erase(pins); + r = ask_password_auto("Please enter security token PIN:", "drive-harddisk", NULL, "fido2-pin", "cryptsetup.fido2-pin", until, flags, &pins); + if (r < 0) + return r; + + STRV_FOREACH(p, pins) { + r = crypt_activate_by_token_pin(cd, name, "systemd-fido2", CRYPT_ANY_TOKEN, *p, strlen(*p), usrptr, activation_flags); + if (r > 0) /* returns unlocked keyslot id on success */ + r = 0; + if (r != -ENOANO) /* needs pin or pin is wrong */ + return r; + } + + flags &= ~ASK_PASSWORD_ACCEPT_CACHED; + for (;;) { + pins = strv_free_erase(pins); + r = ask_password_auto("Please enter security token PIN:", "drive-harddisk", NULL, "fido2-pin", "cryptsetup.fido2-pin", until, flags, &pins); + if (r < 0) + return r; + + STRV_FOREACH(p, pins) { + r = crypt_activate_by_token_pin(cd, name, "systemd-fido2", CRYPT_ANY_TOKEN, *p, strlen(*p), usrptr, activation_flags); + if (r > 0) /* returns unlocked keyslot id on success */ + r = 0; + if (r != -ENOANO) /* needs pin or pin is wrong */ + return r; + } + } +#endif + return r; +} + static int attach_luks_or_plain_or_bitlk_by_fido2( struct crypt_device *cd, const char *name, @@ -750,12 +849,13 @@ static int attach_luks_or_plain_or_bitlk_by_fido2( _cleanup_(erase_and_freep) void *decrypted_key = NULL; _cleanup_(sd_event_unrefp) sd_event *event = NULL; _cleanup_free_ void *discovered_salt = NULL, *discovered_cid = NULL; - size_t discovered_salt_size, discovered_cid_size, cid_size, decrypted_key_size; + size_t discovered_salt_size, discovered_cid_size, decrypted_key_size, cid_size = 0; _cleanup_free_ char *friendly = NULL, *discovered_rp_id = NULL; int keyslot = arg_key_slot, r; - const char *rp_id; - const void *cid; + const char *rp_id = NULL; + const void *cid = NULL; Fido2EnrollFlags required; + bool use_libcryptsetup_plugin = libcryptsetup_plugins_support(); assert(cd); assert(name); @@ -775,7 +875,7 @@ static int attach_luks_or_plain_or_bitlk_by_fido2( * use PIN + UP when needed, and do not configure UV at all. Eventually, we should make this * explicitly configurable. */ required = FIDO2ENROLL_PIN_IF_NEEDED | FIDO2ENROLL_UP_IF_NEEDED | FIDO2ENROLL_UV_OMIT; - } else { + } else if (!use_libcryptsetup_plugin) { r = find_fido2_auto_data( cd, &discovered_rp_id, @@ -810,21 +910,30 @@ static int attach_luks_or_plain_or_bitlk_by_fido2( for (;;) { bool processed = false; - r = acquire_fido2_key( - name, - friendly, - arg_fido2_device, - rp_id, - cid, cid_size, - key_file, arg_keyfile_size, arg_keyfile_offset, - key_data, key_data_size, - until, - arg_headless, - required, - &decrypted_key, &decrypted_key_size, - arg_ask_password_flags); - if (r >= 0) - break; + if (use_libcryptsetup_plugin && !arg_fido2_cid) { + r = attach_luks2_by_fido2(cd, name, until, arg_headless, arg_fido2_device, flags); + if (IN_SET(r, -ENOTUNIQ, -ENXIO, -ENOENT)) + return log_debug_errno(SYNTHETIC_ERRNO(EAGAIN), + "Automatic FIDO2 metadata discovery was not possible because missing or not unique, falling back to traditional unlocking."); + + } else { + r = acquire_fido2_key( + name, + friendly, + arg_fido2_device, + rp_id, + cid, cid_size, + key_file, arg_keyfile_size, arg_keyfile_offset, + key_data, key_data_size, + until, + arg_headless, + required, + &decrypted_key, &decrypted_key_size, + arg_ask_password_flags); + if (r >= 0) + break; + } + if (r != -EAGAIN) /* EAGAIN means: token not found */ return r; @@ -887,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, @@ -904,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; @@ -935,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; @@ -985,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.c b/src/shared/pkcs11-util.c index ff3f245699..27823ab219 100644 --- a/src/shared/pkcs11-util.c +++ b/src/shared/pkcs11-util.c @@ -175,6 +175,55 @@ char *pkcs11_token_model(const CK_TOKEN_INFO *token_info) { return t; } +int pkcs11_token_login_by_pin( + CK_FUNCTION_LIST *m, + CK_SESSION_HANDLE session, + const CK_TOKEN_INFO *token_info, + const char *token_label, + const void *pin, + size_t pin_size) { + + CK_RV rv; + + assert(m); + assert(token_info); + + if (FLAGS_SET(token_info->flags, CKF_PROTECTED_AUTHENTICATION_PATH)) { + rv = m->C_Login(session, CKU_USER, NULL, 0); + if (rv != CKR_OK) + return log_error_errno(SYNTHETIC_ERRNO(EIO), + "Failed to log into security token '%s': %s", token_label, p11_kit_strerror(rv)); + + log_info("Successfully logged into security token '%s' via protected authentication path.", token_label); + return 0; + } + + if (!FLAGS_SET(token_info->flags, CKF_LOGIN_REQUIRED)) { + log_info("No login into security token '%s' required.", token_label); + return 0; + } + + if (!pin) + return -ENOANO; + + rv = m->C_Login(session, CKU_USER, (CK_UTF8CHAR*) pin, pin_size); + if (rv == CKR_OK) { + log_info("Successfully logged into security token '%s'.", token_label); + return 0; + } + + if (rv == CKR_PIN_LOCKED) + return log_error_errno(SYNTHETIC_ERRNO(EPERM), + "PIN has been locked, please reset PIN of security token '%s'.", token_label); + if (!IN_SET(rv, CKR_PIN_INCORRECT, CKR_PIN_LEN_RANGE)) + return log_error_errno(SYNTHETIC_ERRNO(EIO), + "Failed to log into security token '%s': %s", token_label, p11_kit_strerror(rv)); + + log_notice("PIN for token '%s' is incorrect, please try again.", token_label); + + return -ENOLCK; +} + int pkcs11_token_login( CK_FUNCTION_LIST *m, CK_SESSION_HANDLE session, @@ -209,24 +258,12 @@ int pkcs11_token_login( if (uri_result != P11_KIT_URI_OK) return log_warning_errno(SYNTHETIC_ERRNO(EAGAIN), "Failed to format slot URI: %s", p11_kit_uri_message(uri_result)); - if (FLAGS_SET(token_info->flags, CKF_PROTECTED_AUTHENTICATION_PATH)) { - rv = m->C_Login(session, CKU_USER, NULL, 0); - if (rv != CKR_OK) - return log_error_errno(SYNTHETIC_ERRNO(EIO), - "Failed to log into security token '%s': %s", token_label, p11_kit_strerror(rv)); + r = pkcs11_token_login_by_pin(m, session, token_info, token_label, /* pin= */ NULL, 0); + if (r == 0 && ret_used_pin) + *ret_used_pin = NULL; - log_info("Successfully logged into security token '%s' via protected authentication path.", token_label); - if (ret_used_pin) - *ret_used_pin = NULL; - return 0; - } - - if (!FLAGS_SET(token_info->flags, CKF_LOGIN_REQUIRED)) { - log_info("No login into security token '%s' required.", token_label); - if (ret_used_pin) - *ret_used_pin = NULL; - return 0; - } + if (r != -ENOANO) /* pin required */ + return r; token_uri_escaped = cescape(token_uri_string); if (!token_uri_escaped) @@ -278,28 +315,19 @@ int pkcs11_token_login( } STRV_FOREACH(i, passwords) { - rv = m->C_Login(session, CKU_USER, (CK_UTF8CHAR*) *i, strlen(*i)); - if (rv == CKR_OK) { + r = pkcs11_token_login_by_pin(m, session, token_info, token_label, *i, strlen(*i)); + if (r == 0 && ret_used_pin) { + char *c; - if (ret_used_pin) { - char *c; + c = strdup(*i); + if (!c) + return log_oom(); - c = strdup(*i); - if (!c) - return log_oom(); - - *ret_used_pin = c; - } - - log_info("Successfully logged into security token '%s'.", token_label); - return 0; + *ret_used_pin = c; } - if (rv == CKR_PIN_LOCKED) - return log_error_errno(SYNTHETIC_ERRNO(EPERM), - "PIN has been locked, please reset PIN of security token '%s'.", token_label); - if (!IN_SET(rv, CKR_PIN_INCORRECT, CKR_PIN_LEN_RANGE)) - return log_error_errno(SYNTHETIC_ERRNO(EIO), - "Failed to log into security token '%s': %s", token_label, p11_kit_strerror(rv)); + + if (r != -ENOLCK) + return r; /* Referesh the token info, so that we can prompt knowing the new flags if they changed. */ rv = m->C_GetTokenInfo(slotid, &updated_token_info); @@ -309,7 +337,6 @@ int pkcs11_token_login( slotid, p11_kit_strerror(rv)); token_info = &updated_token_info; - log_notice("PIN for token '%s' is incorrect, please try again.", token_label); } } @@ -1154,3 +1181,71 @@ int pkcs11_find_token_auto(char **ret) { "PKCS#11 tokens not supported on this build."); #endif } + +#if HAVE_P11KIT +void pkcs11_crypt_device_callback_data_release(pkcs11_crypt_device_callback_data *data) { + erase_and_free(data->decrypted_key); + + if (data->free_encrypted_key) + free(data->encrypted_key); +} + +int pkcs11_crypt_device_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) { + + pkcs11_crypt_device_callback_data *data = userdata; + CK_OBJECT_HANDLE object; + int r; + + assert(m); + assert(slot_info); + assert(token_info); + assert(uri); + assert(data); + + /* Called for every token matching our URI */ + + r = pkcs11_token_login( + m, + session, + slot_id, + token_info, + data->friendly_name, + "drive-harddisk", + "pkcs11-pin", + "cryptsetup.pkcs11-pin", + data->until, + data->headless, + NULL); + if (r < 0) + return r; + + /* We are likely called during early boot, where entropy is scarce. Mix some data from the PKCS#11 + * token, if it supports that. It should be cheap, given that we already are talking to it anyway and + * shouldn't hurt. */ + (void) pkcs11_token_acquire_rng(m, session); + + 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; +} +#endif diff --git a/src/shared/pkcs11-util.h b/src/shared/pkcs11-util.h index f7f32d34d7..f8195d7a36 100644 --- a/src/shared/pkcs11-util.h +++ b/src/shared/pkcs11-util.h @@ -30,6 +30,7 @@ char *pkcs11_token_label(const CK_TOKEN_INFO *token_info); char *pkcs11_token_manufacturer_id(const CK_TOKEN_INFO *token_info); char *pkcs11_token_model(const CK_TOKEN_INFO *token_info); +int pkcs11_token_login_by_pin(CK_FUNCTION_LIST *m, CK_SESSION_HANDLE session, const CK_TOKEN_INFO *token_info, const char *token_label, const void *pin, size_t pin_size); int pkcs11_token_login(CK_FUNCTION_LIST *m, CK_SESSION_HANDLE session, CK_SLOT_ID slotid, const CK_TOKEN_INFO *token_info, const char *friendly_name, const char *icon_name, const char *key_name, const char *credential_name, usec_t until, bool headless, char **ret_used_pin); int pkcs11_token_find_x509_certificate(CK_FUNCTION_LIST *m, CK_SESSION_HANDLE session, P11KitUri *search_uri, CK_OBJECT_HANDLE *ret_object); @@ -49,7 +50,35 @@ int pkcs11_find_token(const char *pkcs11_uri, pkcs11_find_token_callback_t callb int pkcs11_acquire_certificate(const char *uri, const char *askpw_friendly_name, const char *askpw_icon_name, X509 **ret_cert, char **ret_pin_used); #endif +typedef struct { + const char *friendly_name; + usec_t until; + void *encrypted_key; + size_t encrypted_key_size; + void *decrypted_key; + size_t decrypted_key_size; + bool free_encrypted_key; + bool headless; +} pkcs11_crypt_device_callback_data; + +void pkcs11_crypt_device_callback_data_release(pkcs11_crypt_device_callback_data *data); + +int pkcs11_crypt_device_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); + #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);