diff options
author | Lennart Poettering <lennart@poettering.net> | 2019-08-22 10:21:11 +0200 |
---|---|---|
committer | Lennart Poettering <lennart@poettering.net> | 2019-12-09 19:25:25 +0100 |
commit | 086697094ec7ea5e51b8fb7d545ecbadab6b9a11 (patch) | |
tree | dc434a824079f73521e6f03e5960495168e68fb8 | |
parent | f573629c0bba7cb3cbd49f149945e802c136788a (diff) | |
download | systemd-086697094ec7ea5e51b8fb7d545ecbadab6b9a11.tar.gz |
cryptsetup: add native pkcs#11 support to cryptsetup
This adds a new crypttab option for volumes "pkcs11-uri=" which takes a
PKCS#11 URI. When used the key stored in the line's key file is
decrypted with the private key the PKCS#11 URI indiciates.
This means any smartcard that can store private RSA keys is usable for
unlocking LUKS devices.
-rw-r--r-- | meson.build | 14 | ||||
-rw-r--r-- | src/cryptsetup/cryptsetup-pkcs11.c | 172 | ||||
-rw-r--r-- | src/cryptsetup/cryptsetup-pkcs11.h | 37 | ||||
-rw-r--r-- | src/cryptsetup/cryptsetup.c | 189 |
4 files changed, 386 insertions, 26 deletions
diff --git a/meson.build b/meson.build index 31e7c2aa6d..004f01521c 100644 --- a/meson.build +++ b/meson.build @@ -2009,11 +2009,21 @@ executable('systemd-system-update-generator', install_dir : systemgeneratordir) if conf.get('HAVE_LIBCRYPTSETUP') == 1 + systemd_cryptsetup_sources = files(''' + src/cryptsetup/cryptsetup.c + src/cryptsetup/cryptsetup-pkcs11.h +'''.split()) + + if conf.get('HAVE_P11KIT') == 1 + systemd_cryptsetup_sources += files('src/cryptsetup/cryptsetup-pkcs11.c') + endif + executable('systemd-cryptsetup', - 'src/cryptsetup/cryptsetup.c', + systemd_cryptsetup_sources, include_directories : includes, link_with : [libshared], - dependencies : [libcryptsetup], + dependencies : [libcryptsetup, + libp11kit], install_rpath : rootlibexecdir, install : true, install_dir : rootlibexecdir) diff --git a/src/cryptsetup/cryptsetup-pkcs11.c b/src/cryptsetup/cryptsetup-pkcs11.c new file mode 100644 index 0000000000..c259a766d7 --- /dev/null +++ b/src/cryptsetup/cryptsetup-pkcs11.c @@ -0,0 +1,172 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ + +#include <fcntl.h> +#include <unistd.h> +#include <sys/stat.h> + +#include <p11-kit/p11-kit.h> +#include <p11-kit/uri.h> + +#include "alloc-util.h" +#include "ask-password-api.h" +#include "cryptsetup-pkcs11.h" +#include "escape.h" +#include "fd-util.h" +#include "macro.h" +#include "memory-util.h" +#include "pkcs11-util.h" +#include "stat-util.h" +#include "strv.h" + +static int load_key_file( + const char *key_file, + size_t key_file_size, + uint64_t key_file_offset, + void **ret_encrypted_key, + size_t *ret_encrypted_key_size) { + + _cleanup_(erase_and_freep) char *buffer = NULL; + _cleanup_close_ int fd = -1; + ssize_t n; + int r; + + assert(key_file); + assert(ret_encrypted_key); + assert(ret_encrypted_key_size); + + fd = open(key_file, O_RDONLY|O_CLOEXEC); + if (fd < 0) + return log_error_errno(errno, "Failed to load encrypted PKCS#11 key: %m"); + + if (key_file_size == 0) { + struct stat st; + + if (fstat(fd, &st) < 0) + return log_error_errno(errno, "Failed to stat key file: %m"); + + r = stat_verify_regular(&st); + if (r < 0) + return log_error_errno(r, "Key file is not a regular file: %m"); + + if (st.st_size == 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Key file is empty, refusing."); + if ((uint64_t) st.st_size > SIZE_MAX) + return log_error_errno(SYNTHETIC_ERRNO(ERANGE), "Key file too large, refusing."); + + if (key_file_offset >= (uint64_t) st.st_size) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Key file offset too large for file, refusing."); + + key_file_size = st.st_size - key_file_offset; + } + + buffer = malloc(key_file_size); + if (!buffer) + return log_oom(); + + if (key_file_offset > 0) + n = pread(fd, buffer, key_file_size, key_file_offset); + else + n = read(fd, buffer, key_file_size); + if (n < 0) + return log_error_errno(errno, "Failed to read PKCS#11 key file: %m"); + if (n == 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Empty encrypted key found, refusing."); + + *ret_encrypted_key = TAKE_PTR(buffer); + *ret_encrypted_key_size = (size_t) n; + + return 0; +} + +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; +}; + +static void pkcs11_callback_data_release(struct pkcs11_callback_data *data) { + free(data->decrypted_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", data->until, 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 1; +} + +int decrypt_pkcs11_key( + const char *friendly_name, + const char *pkcs11_uri, + const char *key_file, + size_t key_file_size, + uint64_t key_file_offset, + usec_t until, + void **ret_decrypted_key, + size_t *ret_decrypted_key_size) { + + _cleanup_(pkcs11_callback_data_release) struct pkcs11_callback_data data = { + .friendly_name = friendly_name, + .until = until, + }; + int r; + + assert(friendly_name); + assert(pkcs11_uri); + assert(key_file); + assert(ret_decrypted_key); + assert(ret_decrypted_key_size); + + /* The functions called here log about all errors, except for EAGAIN which means "token not found right now" */ + + r = load_key_file(key_file, key_file_size, key_file_offset, &data.encrypted_key, &data.encrypted_key_size); + if (r < 0) + return r; + + r = pkcs11_find_token(pkcs11_uri, 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; +} diff --git a/src/cryptsetup/cryptsetup-pkcs11.h b/src/cryptsetup/cryptsetup-pkcs11.h new file mode 100644 index 0000000000..264ccb66b1 --- /dev/null +++ b/src/cryptsetup/cryptsetup-pkcs11.h @@ -0,0 +1,37 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ +#pragma once + +#include <sys/types.h> + +#include "log.h" +#include "time-util.h" + +#if HAVE_P11KIT + +int decrypt_pkcs11_key( + const char *friendly_name, + const char *pkcs11_uri, + const char *key_file, + size_t key_file_size, + uint64_t key_file_offset, + usec_t until, + void **ret_decrypted_key, + size_t *ret_decrypted_key_size); + +#else + +static inline int decrypt_pkcs11_key( + const char *friendly_name, + const char *pkcs11_uri, + const char *key_file, + size_t key_file_size, + uint64_t key_file_offset, + usec_t until, + void **ret_decrypted_key, + size_t *ret_decrypted_key_size) { + + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "PKCS#11 Token support not available."); +} + +#endif diff --git a/src/cryptsetup/cryptsetup.c b/src/cryptsetup/cryptsetup.c index 19f075dfeb..328873e0e1 100644 --- a/src/cryptsetup/cryptsetup.c +++ b/src/cryptsetup/cryptsetup.c @@ -12,16 +12,19 @@ #include "alloc-util.h" #include "ask-password-api.h" #include "crypt-util.h" +#include "cryptsetup-pkcs11.h" #include "device-util.h" #include "escape.h" #include "fileio.h" #include "fstab-util.h" +#include "hexdecoct.h" #include "log.h" #include "main-func.h" #include "mount-util.h" #include "nulstr-util.h" #include "parse-util.h" #include "path-util.h" +#include "pkcs11-util.h" #include "pretty-print.h" #include "string-util.h" #include "strv.h" @@ -54,11 +57,13 @@ static char **arg_tcrypt_keyfiles = NULL; static uint64_t arg_offset = 0; static uint64_t arg_skip = 0; static usec_t arg_timeout = USEC_INFINITY; +static char *arg_pkcs11_uri = NULL; STATIC_DESTRUCTOR_REGISTER(arg_cipher, freep); STATIC_DESTRUCTOR_REGISTER(arg_hash, freep); STATIC_DESTRUCTOR_REGISTER(arg_header, freep); STATIC_DESTRUCTOR_REGISTER(arg_tcrypt_keyfiles, strv_freep); +STATIC_DESTRUCTOR_REGISTER(arg_pkcs11_uri, freep); /* Options Debian's crypttab knows we don't: @@ -228,6 +233,15 @@ static int parse_one_option(const char *option) { if (r < 0) return log_error_errno(r, "Failed to parse %s: %m", option); + } else if ((val = startswith(option, "pkcs11-uri="))) { + + if (!pkcs11_uri_valid(val)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "pkcs11-uri= parameter expects a PKCS#11 URI, refusing"); + + r = free_and_strdup(&arg_pkcs11_uri, val); + if (r < 0) + return log_oom(); + } else if (!streq(option, "x-initrd.attach")) log_warning("Encountered unknown /etc/crypttab option '%s', ignoring.", option); @@ -314,28 +328,19 @@ static char *disk_mount_point(const char *label) { return NULL; } -static int get_password(const char *vol, const char *src, usec_t until, bool accept_cached, char ***ret) { - _cleanup_free_ char *description = NULL, *name_buffer = NULL, *mount_point = NULL, *text = NULL, *disk_path = NULL; - _cleanup_strv_free_erase_ char **passwords = NULL; - const char *name = NULL; - char **p, *id; - int r = 0; +static char *friendly_disk_name(const char *src, const char *vol) { + _cleanup_free_ char *description = NULL, *mount_point = NULL; + char *name_buffer = NULL; + int r; - assert(vol); assert(src); - assert(ret); + assert(vol); description = disk_description(src); mount_point = disk_mount_point(vol); - disk_path = cescape(src); - if (!disk_path) - return log_oom(); - + /* If the description string is simply the volume name, then let's not show this twice */ if (description && streq(vol, description)) - /* If the description string is simply the - * volume name, then let's not show this - * twice */ description = mfree(description); if (mount_point && description) @@ -344,13 +349,39 @@ static int get_password(const char *vol, const char *src, usec_t until, bool acc r = asprintf(&name_buffer, "%s on %s", vol, mount_point); else if (description) r = asprintf(&name_buffer, "%s (%s)", description, vol); - + else + return strdup(vol); if (r < 0) + return NULL; + + return name_buffer; +} + +static int get_password( + const char *vol, + const char *src, + usec_t until, + bool accept_cached, + char ***ret) { + + _cleanup_free_ char *friendly = NULL, *text = NULL, *disk_path = NULL; + _cleanup_strv_free_erase_ char **passwords = NULL; + char **p, *id; + int r = 0; + + assert(vol); + assert(src); + assert(ret); + + friendly = friendly_disk_name(src, vol); + if (!friendly) return log_oom(); - name = name_buffer ? name_buffer : vol; + if (asprintf(&text, "Please enter passphrase for disk %s:", friendly) < 0) + return log_oom(); - if (asprintf(&text, "Please enter passphrase for disk %s:", name) < 0) + disk_path = cescape(src); + if (!disk_path) return log_oom(); id = strjoina("cryptsetup:", disk_path); @@ -366,7 +397,7 @@ static int get_password(const char *vol, const char *src, usec_t until, bool acc assert(strv_length(passwords) == 1); - if (asprintf(&text, "Please enter passphrase for disk %s (verification):", name) < 0) + if (asprintf(&text, "Please enter passphrase for disk %s (verification):", friendly) < 0) return log_oom(); id = strjoina("cryptsetup-verification:", disk_path); @@ -424,6 +455,11 @@ static int attach_tcrypt( assert(name); assert(key_file || (passwords && passwords[0])); + if (arg_pkcs11_uri) { + log_error("Sorry, but tcrypt devices are currently not supported in conjunction with pkcs11 support."); + return -EAGAIN; /* Ask for a regular password */ + } + if (arg_tcrypt_hidden) params.flags |= CRYPT_TCRYPT_HIDDEN_HEADER; @@ -467,14 +503,14 @@ static int attach_luks_or_plain( const char *name, const char *key_file, char **passwords, - uint32_t flags) { + uint32_t flags, + usec_t until) { int r = 0; bool pass_volume_key = false; assert(cd); assert(name); - assert(key_file || passwords); if ((!arg_type && !crypt_get_type(cd)) || streq_ptr(arg_type, CRYPT_PLAIN)) { struct crypt_params_plain params = { @@ -528,7 +564,111 @@ static int attach_luks_or_plain( crypt_get_volume_key_size(cd)*8, crypt_get_device_name(cd)); - if (key_file) { + if (arg_pkcs11_uri) { + _cleanup_(sd_device_monitor_unrefp) sd_device_monitor *monitor = NULL; + _cleanup_(sd_event_unrefp) sd_event *event = NULL; + _cleanup_free_ void *decrypted_key = NULL; + _cleanup_free_ char *friendly = NULL; + size_t decrypted_key_size = 0; + + if (!key_file) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "PKCS#11 mode selected but no key file specified, refusing."); + + friendly = friendly_disk_name(crypt_get_device_name(cd), name); + if (!friendly) + return log_oom(); + + for (;;) { + bool processed = false; + + r = decrypt_pkcs11_key( + friendly, + arg_pkcs11_uri, + key_file, + arg_keyfile_size, arg_keyfile_offset, + until, + &decrypted_key, &decrypted_key_size); + if (r >= 0) + break; + if (r != -EAGAIN) /* EAGAIN means: token not found */ + return r; + + if (!monitor) { + /* We didn't find the token. In this case, watch for it via udev. Let's + * create an event loop and monitor first. */ + + assert(!event); + + r = sd_event_default(&event); + if (r < 0) + return log_error_errno(r, "Failed to allocate event loop: %m"); + + r = sd_device_monitor_new(&monitor); + if (r < 0) + return log_error_errno(r, "Failed to allocate device monitor: %m"); + + r = sd_device_monitor_filter_add_match_tag(monitor, "security-device"); + if (r < 0) + return log_error_errno(r, "Failed to configure device monitor: %m"); + + r = sd_device_monitor_attach_event(monitor, event); + if (r < 0) + return log_error_errno(r, "Failed to attach device monitor: %m"); + + r = sd_device_monitor_start(monitor, NULL, NULL); + if (r < 0) + return log_error_errno(r, "Failed to start device monitor: %m"); + + log_notice("Security token %s not present for unlocking volume %s, please plug it in.", + arg_pkcs11_uri, friendly); + + /* Let's immediately rescan in case the token appeared in the time we needed + * to create and configure the monitor */ + continue; + } + + for (;;) { + /* Wait for one event, and then eat all subsequent events until there are no + * further ones */ + r = sd_event_run(event, processed ? 0 : UINT64_MAX); + if (r < 0) + return log_error_errno(r, "Failed to run event loop: %m"); + if (r == 0) + break; + + processed = true; + } + + log_debug("Got one or more potentially relevant udev events, rescanning PKCS#11..."); + } + + if (pass_volume_key) + r = crypt_activate_by_volume_key(cd, name, decrypted_key, decrypted_key_size, flags); + else { + _cleanup_free_ char *base64_encoded = NULL; + + /* Before using this key as passphrase we base64 encode it. Why? For compatibility + * with homed's PKCS#11 hookup: there we want to use the key we acquired through + * PKCS#11 for other authentication/decryption mechanisms too, and some of them do + * not not take arbitrary binary blobs, but require NUL-terminated strings — most + * importantly UNIX password hashes. Hence, for compatibility we want to use a string + * without embedded NUL here too, and that's easiest to generate from a binary blob + * via base64 encoding. */ + + r = base64mem(decrypted_key, decrypted_key_size, &base64_encoded); + if (r < 0) + return log_oom(); + + r = crypt_activate_by_passphrase(cd, name, arg_key_slot, base64_encoded, strlen(base64_encoded), flags); + } + if (r == -EPERM) { + log_error_errno(r, "Failed to activate with PKCS#11 decrypted key. (Key incorrect?)"); + return -EAGAIN; /* log actual error, but return EAGAIN */ + } + if (r < 0) + return log_error_errno(r, "Failed to activate with PKCS#11 acquired key: %m"); + + } else if (key_file) { r = crypt_activate_by_keyfile_device_offset(cd, name, arg_key_slot, key_file, arg_keyfile_size, arg_keyfile_offset, flags); if (r == -EPERM) { log_error_errno(r, "Failed to activate with key file '%s'. (Key data incorrect?)", key_file); @@ -717,7 +857,7 @@ static int run(int argc, char *argv[]) { for (tries = 0; arg_tries == 0 || tries < arg_tries; tries++) { _cleanup_strv_free_erase_ char **passwords = NULL; - if (!key_file) { + if (!key_file && !arg_pkcs11_uri) { r = get_password(argv[2], argv[3], until, tries == 0 && !arg_verify, &passwords); if (r == -EAGAIN) continue; @@ -728,7 +868,7 @@ static int run(int argc, char *argv[]) { if (streq_ptr(arg_type, CRYPT_TCRYPT)) r = attach_tcrypt(cd, argv[2], key_file, passwords, flags); else - r = attach_luks_or_plain(cd, argv[2], key_file, passwords, flags); + r = attach_luks_or_plain(cd, argv[2], key_file, passwords, flags, until); if (r >= 0) break; if (r != -EAGAIN) @@ -736,6 +876,7 @@ static int run(int argc, char *argv[]) { /* Passphrase not correct? Let's try again! */ key_file = NULL; + arg_pkcs11_uri = NULL; } if (arg_tries != 0 && tries >= arg_tries) |