diff --git a/man/crypttab.xml b/man/crypttab.xml index 9b6fffd154..3942fe67f9 100644 --- a/man/crypttab.xml +++ b/man/crypttab.xml @@ -41,7 +41,7 @@ character are ignored. Each of the remaining lines describes one encrypted block device. Fields are delimited by white space. - Each line is in the formname encrypted-device password options + Each line is in the formvolume-name encrypted-device key-file options The first two fields are mandatory, the remaining two are optional. @@ -53,24 +53,20 @@ it is opened as a LUKS device; otherwise, it is assumed to be in raw dm-crypt (plain mode) format. - The first field contains the name of the resulting encrypted - block device; the device is set up within - /dev/mapper/. + The first field contains the name of the resulting encrypted volume; its block device is set up + below /dev/mapper/. The second field contains a path to the underlying block device or file, or a specification of a block device via UUID= followed by the UUID. - The third field specifies the encryption password. If the - field is not present or the password is set to - none or -, the password has - to be manually entered during system boot. Otherwise, the field is - interpreted as an absolute path to a file containing the encryption - password. For swap encryption, /dev/urandom - or the hardware device /dev/hw_random can be - used as the password file; using /dev/random - may prevent boot completion if the system does not have enough - entropy to generate a truly random encryption key. + The third field specifies an absolute path to a file to read the encryption key from. If the field + is not present or set to none or -, a key file named after the + volume to unlock (i.e. the first column of the line), suffixed with .key is + automatically loaded from the /etc/cryptsetup-keys.d/ and + /run/cryptsetup-keys.d/ directories, if present. Otherwise, the password has to be + manually entered during system boot. For swap encryption, /dev/urandom may be used + as key file. The fourth field, if present, is a comma-delimited list of options. The following options are recognized: @@ -138,6 +134,15 @@ size is then given by the key size. + + + + If enabled, the specified key file is erased after the volume is activated or when + activation fails. This is in particular useful when the key file is only acquired transiently before + activation (e.g. via a file in /run/, generated by a service running before + activation), and shall be removed after use. Defaults to off. + + @@ -431,6 +436,15 @@ before it is used to unlock the LUKS volume. + + + + Takes a boolean argument. If enabled, right before asking the user for a password it + is first attempted to unlock the volume with an empty password. This is useful for systems that are + initialized with an encrypted volume with only an empty password set, which shall be replaced with a + suitable password during first boot, but after activation. + + diff --git a/man/systemd-cryptsetup@.service.xml b/man/systemd-cryptsetup@.service.xml index 0324a67440..47051b9cef 100644 --- a/man/systemd-cryptsetup@.service.xml +++ b/man/systemd-cryptsetup@.service.xml @@ -44,6 +44,32 @@ At early boot and when the system manager configuration is reloaded, /etc/crypttab is translated into systemd-cryptsetup@.service units by systemd-cryptsetup-generator8. + + In order to unlock a volume a password or binary key is + required. systemd-cryptsetup@.service tries to acquire a suitable password or binary + key via the following mechanisms, tried in order: + + + If a key file is explicitly configured (via the third column in + /etc/crypttab), a key read from it is used. If a PKCS#11 token is configured + (using the pkcs11-uri= option) the key is decrypted before use. + + If no key file is configured explicitly this way, a key file is automatically loaded + from /etc/cryptsetup-keys.d/volume.key and + /run/cryptsetup-keys.d/volume.key, if present. Here + too, if a PKCS#11 token is configured, any key found this way is decrypted before + use. + + If the try-empty-password option is specified it is then attempted + to unlock the volume with an empty password. + + The kernel keyring is then checked for a suitable cached password from previous + attempts. + + Finally, the user is queried for a password, possibly multiple times. + + + If no suitable key may be acquired via any of the mechanisms describes above, volume activation fails. diff --git a/man/yubikey-crypttab.sh b/man/yubikey-crypttab.sh index c0717b364e..651246d6a1 100644 --- a/man/yubikey-crypttab.sh +++ b/man/yubikey-crypttab.sh @@ -23,8 +23,9 @@ dd if=/dev/urandom of=plaintext.bin bs=128 count=1 base64 < plaintext.bin | tr -d '\n\r\t ' > plaintext.base64 # Encrypt this newly generated (binary) LUKS decryption key using the public key whose private key is on the -# Yubikey, store the result in /etc/encrypted-luks-key.bin, where we'll look for it during boot. -sudo openssl rsautl -encrypt -pubin -inkey pubkey.pem -in plaintext.bin -out /etc/encrypted-luks-key.bin +# Yubikey, store the result in /etc/cryptsetup-keys.d/mytest.key, where we'll look for it during boot. +mkdir -p /etc/cryptsetup-keys.d +sudo openssl rsautl -encrypt -pubin -inkey pubkey.pem -in plaintext.bin -out /etc/cryptsetup-keys.d/mytest.key # Configure the LUKS decryption key on the LUKS device. We use very low pbkdf settings since the key already # has quite a high quality (it comes directly from /dev/urandom after all), and thus we don't need to do much @@ -40,8 +41,10 @@ shred -u plaintext.bin plaintext.base64 rm pubkey.pem # Test: Let's run systemd-cryptsetup to test if this all worked. The option string should contain the full -# PKCS#11 URI we have in the clipboard, it tells the tool how to decipher the encrypted LUKS key. -sudo systemd-cryptsetup attach mytest /dev/sdXn /etc/encrypted-luks-key.bin 'pkcs11-uri=pkcs11:…' +# PKCS#11 URI we have in the clipboard; it tells the tool how to decipher the encrypted LUKS key. Note that +# systemd-cryptsetup automatically searches for the encrypted key in /etc/cryptsetup-keys.d/, hence we do +# not need to specify the key file path explicitly here. +sudo systemd-cryptsetup attach mytest /dev/sdXn - 'pkcs11-uri=pkcs11:…' # If that worked, let's now add the same line persistently to /etc/crypttab, for the future. -sudo bash -c 'echo "mytest /dev/sdXn /etc/encrypted-luks-key \'pkcs11-uri=pkcs11:…\'" >> /etc/crypttab' +sudo bash -c 'echo "mytest /dev/sdXn - \'pkcs11-uri=pkcs11:…\'" >> /etc/crypttab' diff --git a/meson.build b/meson.build index 121148f8dd..f485616d10 100644 --- a/meson.build +++ b/meson.build @@ -2227,8 +2227,10 @@ executable( if conf.get('HAVE_LIBCRYPTSETUP') == 1 systemd_cryptsetup_sources = files(''' - src/cryptsetup/cryptsetup.c src/cryptsetup/cryptsetup-pkcs11.h + src/cryptsetup/cryptsetup-util.c + src/cryptsetup/cryptsetup-util.h + src/cryptsetup/cryptsetup.c '''.split()) if conf.get('HAVE_P11KIT') == 1 diff --git a/src/basic/fs-util.c b/src/basic/fs-util.c index e13d75de2d..2c5bfb3263 100644 --- a/src/basic/fs-util.c +++ b/src/basic/fs-util.c @@ -23,6 +23,7 @@ #include "parse-util.h" #include "path-util.h" #include "process-util.h" +#include "random-util.h" #include "stat-util.h" #include "stdio-util.h" #include "string-util.h" @@ -1303,11 +1304,13 @@ void unlink_tempfilep(char (*p)[]) { (void) unlink_noerrno(*p); } -int unlinkat_deallocate(int fd, const char *name, int flags) { +int unlinkat_deallocate(int fd, const char *name, UnlinkDeallocateFlags flags) { _cleanup_close_ int truncate_fd = -1; struct stat st; off_t l, bs; + assert((flags & ~(UNLINK_REMOVEDIR|UNLINK_ERASE)) == 0); + /* Operates like unlinkat() but also deallocates the file contents if it is a regular file and there's no other * link to it. This is useful to ensure that other processes that might have the file open for reading won't be * able to keep the data pinned on disk forever. This call is particular useful whenever we execute clean-up @@ -1324,7 +1327,7 @@ int unlinkat_deallocate(int fd, const char *name, int flags) { * Note that we attempt deallocation, but failure to succeed with that is not considered fatal, as long as the * primary job – to delete the file – is accomplished. */ - if ((flags & AT_REMOVEDIR) == 0) { + if (!FLAGS_SET(flags, UNLINK_REMOVEDIR)) { truncate_fd = openat(fd, name, O_WRONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW|O_NONBLOCK); if (truncate_fd < 0) { @@ -1340,7 +1343,7 @@ int unlinkat_deallocate(int fd, const char *name, int flags) { } } - if (unlinkat(fd, name, flags) < 0) + if (unlinkat(fd, name, FLAGS_SET(flags, UNLINK_REMOVEDIR) ? AT_REMOVEDIR : 0) < 0) return -errno; if (truncate_fd < 0) /* Don't have a file handle, can't do more ☹️ */ @@ -1351,7 +1354,45 @@ int unlinkat_deallocate(int fd, const char *name, int flags) { return 0; } - if (!S_ISREG(st.st_mode) || st.st_blocks == 0 || st.st_nlink > 0) + if (!S_ISREG(st.st_mode)) + return 0; + + if (FLAGS_SET(flags, UNLINK_ERASE) && st.st_size > 0 && st.st_nlink == 0) { + uint64_t left = st.st_size; + char buffer[64 * 1024]; + + /* If erasing is requested, let's overwrite the file with random data once before deleting + * it. This isn't going to give you shred(1) semantics, but hopefully should be good enough + * for stuff backed by tmpfs at least. + * + * Note that we only erase like this if the link count of the file is zero. If it is higer it + * is still linked by someone else and we'll leave it to them to remove it securely + * eventually! */ + + random_bytes(buffer, sizeof(buffer)); + + while (left > 0) { + ssize_t n; + + n = write(truncate_fd, buffer, MIN(sizeof(buffer), left)); + if (n < 0) { + log_debug_errno(errno, "Failed to erase data in file '%s', ignoring.", name); + break; + } + + assert(left >= (size_t) n); + left -= n; + } + + /* Let's refresh metadata */ + if (fstat(truncate_fd, &st) < 0) { + log_debug_errno(errno, "Failed to stat file '%s' for deallocation, ignoring: %m", name); + return 0; + } + } + + /* Don't dallocate if there's nothing to deallocate or if the file is linked elsewhere */ + if (st.st_blocks == 0 || st.st_nlink > 0) return 0; /* If this is a regular file, it actually took up space on disk and there are no other links it's time to diff --git a/src/basic/fs-util.h b/src/basic/fs-util.h index 0889239829..dd101c61cc 100644 --- a/src/basic/fs-util.h +++ b/src/basic/fs-util.h @@ -113,7 +113,13 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(char*, unlink_and_free); int access_fd(int fd, int mode); void unlink_tempfilep(char (*p)[]); -int unlinkat_deallocate(int fd, const char *name, int flags); + +typedef enum UnlinkDeallocateFlags { + UNLINK_REMOVEDIR = 1 << 0, + UNLINK_ERASE = 1 << 1, +} UnlinkDeallocateFlags; + +int unlinkat_deallocate(int fd, const char *name, UnlinkDeallocateFlags flags); int fsync_directory_of_file(int fd); int fsync_full(int fd); diff --git a/src/cryptsetup/cryptsetup-generator.c b/src/cryptsetup/cryptsetup-generator.c index 5724f88d0a..7974c19a44 100644 --- a/src/cryptsetup/cryptsetup-generator.c +++ b/src/cryptsetup/cryptsetup-generator.c @@ -188,7 +188,11 @@ static int print_dependencies(FILE *f, const char* device_path) { /* None, nothing to do */ return 0; - if (PATH_IN_SET(device_path, "/dev/urandom", "/dev/random", "/dev/hw_random")) { + if (PATH_IN_SET(device_path, + "/dev/urandom", + "/dev/random", + "/dev/hw_random", + "/dev/hwrng")) { /* RNG device, add random dep */ fputs("After=systemd-random-seed.service\n", f); return 0; @@ -209,7 +213,9 @@ static int print_dependencies(FILE *f, const char* device_path) { if (r < 0) return log_error_errno(r, "Failed to generate unit name: %m"); - fprintf(f, "After=%1$s\nRequires=%1$s\n", unit); + fprintf(f, + "After=%1$s\n" + "Requires=%1$s\n", unit); } else { /* Regular file, add mount dependency */ _cleanup_free_ char *escaped_path = specifier_escape(device_path); diff --git a/src/cryptsetup/cryptsetup-pkcs11.c b/src/cryptsetup/cryptsetup-pkcs11.c index ec9186a6aa..642a1b7d11 100644 --- a/src/cryptsetup/cryptsetup-pkcs11.c +++ b/src/cryptsetup/cryptsetup-pkcs11.c @@ -10,6 +10,7 @@ #include "alloc-util.h" #include "ask-password-api.h" #include "cryptsetup-pkcs11.h" +#include "cryptsetup-util.h" #include "escape.h" #include "fd-util.h" #include "format-util.h" @@ -19,73 +20,6 @@ #include "stat-util.h" #include "strv.h" -#define KEY_FILE_SIZE_MAX (16U*1024U*1024U) /* 16 MiB */ - -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 > KEY_FILE_SIZE_MAX) { - char buf1[FORMAT_BYTES_MAX], buf2[FORMAT_BYTES_MAX]; - return log_error_errno(SYNTHETIC_ERRNO(ERANGE), - "Key file larger (%s) than allowed maximum size (%s), refusing.", - format_bytes(buf1, sizeof(buf1), st.st_size), - format_bytes(buf2, sizeof(buf2), KEY_FILE_SIZE_MAX)); - } - - 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; @@ -93,11 +27,14 @@ struct pkcs11_callback_data { size_t encrypted_key_size; void *decrypted_key; size_t decrypted_key_size; + bool free_encrypted_key; }; static void pkcs11_callback_data_release(struct pkcs11_callback_data *data) { free(data->decrypted_key); - free(data->encrypted_key); + + if (data->free_encrypted_key) + free(data->encrypted_key); } static int pkcs11_callback( @@ -160,9 +97,11 @@ static int pkcs11_callback( int decrypt_pkcs11_key( const char *friendly_name, const char *pkcs11_uri, - const char *key_file, + const char *key_file, /* We either expect key_file and associated parameters to be set (for file keys) … */ size_t key_file_size, uint64_t key_file_offset, + const void *key_data, /* … or key_data and key_data_size (for literal keys) */ + size_t key_data_size, usec_t until, void **ret_decrypted_key, size_t *ret_decrypted_key_size) { @@ -175,15 +114,24 @@ int decrypt_pkcs11_key( assert(friendly_name); assert(pkcs11_uri); - assert(key_file); + assert(key_file || key_data); 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; + if (key_data) { + data.encrypted_key = (void*) key_data; + data.encrypted_key_size = key_data_size; + + data.free_encrypted_key = false; + } else { + r = load_key_file(key_file, NULL, key_file_size, key_file_offset, &data.encrypted_key, &data.encrypted_key_size); + if (r < 0) + return r; + + data.free_encrypted_key = true; + } r = pkcs11_find_token(pkcs11_uri, pkcs11_callback, &data); if (r < 0) diff --git a/src/cryptsetup/cryptsetup-pkcs11.h b/src/cryptsetup/cryptsetup-pkcs11.h index 264ccb66b1..af2487e75b 100644 --- a/src/cryptsetup/cryptsetup-pkcs11.h +++ b/src/cryptsetup/cryptsetup-pkcs11.h @@ -14,6 +14,8 @@ int decrypt_pkcs11_key( const char *key_file, size_t key_file_size, uint64_t key_file_offset, + const void *key_data, + size_t key_data_size, usec_t until, void **ret_decrypted_key, size_t *ret_decrypted_key_size); @@ -26,6 +28,8 @@ static inline int decrypt_pkcs11_key( const char *key_file, size_t key_file_size, uint64_t key_file_offset, + const void *key_data, + size_t key_data_size, usec_t until, void **ret_decrypted_key, size_t *ret_decrypted_key_size) { diff --git a/src/cryptsetup/cryptsetup-util.c b/src/cryptsetup/cryptsetup-util.c new file mode 100644 index 0000000000..8ae70a5d10 --- /dev/null +++ b/src/cryptsetup/cryptsetup-util.c @@ -0,0 +1,110 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ + +#include + +#include "cryptsetup-util.h" +#include "fd-util.h" +#include "format-util.h" +#include "memory-util.h" +#include "path-util.h" +#include "stat-util.h" +#include "strv.h" + +#define KEY_FILE_SIZE_MAX (16U*1024U*1024U) /* 16 MiB */ + +int load_key_file( + const char *key_file, + char **search_path, + size_t key_file_size, + uint64_t key_file_offset, + void **ret_key, + size_t *ret_key_size) { + + _cleanup_(erase_and_freep) char *buffer = NULL; + _cleanup_free_ char *discovered_path = NULL; + _cleanup_close_ int fd = -1; + ssize_t n; + int r; + + assert(key_file); + assert(ret_key); + assert(ret_key_size); + + if (strv_isempty(search_path) || path_is_absolute(key_file)) { + fd = open(key_file, O_RDONLY|O_CLOEXEC); + if (fd < 0) + return log_error_errno(errno, "Failed to load key file '%s': %m", key_file); + } else { + char **i; + + STRV_FOREACH(i, search_path) { + _cleanup_free_ char *joined; + + joined = path_join(*i, key_file); + if (!joined) + return log_oom(); + + fd = open(joined, O_RDONLY|O_CLOEXEC); + if (fd >= 0) { + discovered_path = TAKE_PTR(joined); + break; + } + if (errno != ENOENT) + return log_error_errno(errno, "Failed to load key file '%s': %m", joined); + } + + if (!discovered_path) { + /* Search path supplied, but file not found, report by returning NULL, but not failing */ + *ret_key = NULL; + *ret_key_size = 0; + return 0; + } + + assert(fd >= 0); + key_file = discovered_path; + } + + if (key_file_size == 0) { + struct stat st; + + if (fstat(fd, &st) < 0) + return log_error_errno(errno, "Failed to stat key file '%s': %m", key_file); + + 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 > KEY_FILE_SIZE_MAX) { + char buf1[FORMAT_BYTES_MAX], buf2[FORMAT_BYTES_MAX]; + return log_error_errno(SYNTHETIC_ERRNO(ERANGE), + "Key file larger (%s) than allowed maximum size (%s), refusing.", + format_bytes(buf1, sizeof(buf1), st.st_size), + format_bytes(buf2, sizeof(buf2), KEY_FILE_SIZE_MAX)); + } + + 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 key file '%s': %m", key_file); + if (n == 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Empty encrypted key found, refusing."); + + *ret_key = TAKE_PTR(buffer); + *ret_key_size = (size_t) n; + + return 1; +} diff --git a/src/cryptsetup/cryptsetup-util.h b/src/cryptsetup/cryptsetup-util.h new file mode 100644 index 0000000000..7bb78227dc --- /dev/null +++ b/src/cryptsetup/cryptsetup-util.h @@ -0,0 +1,13 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ +#pragma once + +#include +#include + +int load_key_file( + const char *key_file, + char **search_path, + size_t key_file_size, + uint64_t key_file_offset, + void **ret_key, + size_t *ret_key_size); diff --git a/src/cryptsetup/cryptsetup.c b/src/cryptsetup/cryptsetup.c index 860b29b3b6..8c3eafbd61 100644 --- a/src/cryptsetup/cryptsetup.c +++ b/src/cryptsetup/cryptsetup.c @@ -13,13 +13,16 @@ #include "ask-password-api.h" #include "crypt-util.h" #include "cryptsetup-pkcs11.h" +#include "cryptsetup-util.h" #include "device-util.h" #include "escape.h" #include "fileio.h" +#include "fs-util.h" #include "fstab-util.h" #include "hexdecoct.h" #include "log.h" #include "main-func.h" +#include "memory-util.h" #include "mount-util.h" #include "nulstr-util.h" #include "parse-util.h" @@ -42,6 +45,8 @@ static unsigned arg_sector_size = CRYPT_SECTOR_SIZE; static int arg_key_slot = CRYPT_ANY_SLOT; static unsigned arg_keyfile_size = 0; static uint64_t arg_keyfile_offset = 0; +static bool arg_keyfile_erase = false; +static bool arg_try_empty_password = false; static char *arg_hash = NULL; static char *arg_header = NULL; static unsigned arg_tries = 3; @@ -67,12 +72,14 @@ STATIC_DESTRUCTOR_REGISTER(arg_pkcs11_uri, freep); /* Options Debian's crypttab knows we don't: - precheck= check= checkargs= - noearly= - loud= + noearly + loud + quiet keyscript= + tmp= (the version without argument is supported) + initramfs */ static int parse_one_option(const char *option) { @@ -126,7 +133,8 @@ static int parse_one_option(const char *option) { return 0; } - } else if ((val = startswith(option, "key-slot="))) { + } else if ((val = startswith(option, "key-slot=")) || + (val = startswith(option, "keyslot="))) { arg_type = ANY_LUKS; r = safe_atoi(val, &arg_key_slot); @@ -160,7 +168,20 @@ static int parse_one_option(const char *option) { return 0; } - } else if ((val = startswith(option, "hash="))) { + } else if ((val = startswith(option, "keyfile-erase="))) { + + r = parse_boolean(val); + if (r < 0) { + log_error_errno(r, "Failed to parse %s, ignoring: %m", option); + return 0; + } + + arg_keyfile_erase = r; + + } else if (streq(option, "keyfile-erase")) + arg_keyfile_erase = true; + + else if ((val = startswith(option, "hash="))) { r = free_and_strdup(&arg_hash, val); if (r < 0) return log_oom(); @@ -202,13 +223,13 @@ static int parse_one_option(const char *option) { arg_type = ANY_LUKS; else if (streq(option, "tcrypt")) arg_type = CRYPT_TCRYPT; - else if (streq(option, "tcrypt-hidden")) { + else if (STR_IN_SET(option, "tcrypt-hidden", "tcrypthidden")) { arg_type = CRYPT_TCRYPT; arg_tcrypt_hidden = true; } else if (streq(option, "tcrypt-system")) { arg_type = CRYPT_TCRYPT; arg_tcrypt_system = true; - } else if (streq(option, "tcrypt-veracrypt")) { + } else if (STR_IN_SET(option, "tcrypt-veracrypt", "veracrypt")) { arg_type = CRYPT_TCRYPT; arg_tcrypt_veracrypt = true; } else if (STR_IN_SET(option, "plain", "swap", "tmp")) @@ -242,7 +263,20 @@ static int parse_one_option(const char *option) { if (r < 0) return log_oom(); - } else if (!streq(option, "x-initrd.attach")) + } else if ((val = startswith(option, "try-empty-password="))) { + + r = parse_boolean(val); + if (r < 0) { + log_error_errno(r, "Failed to parse %s, ignoring: %m", option); + return 0; + } + + arg_try_empty_password = r; + + } else if (streq(option, "try-empty-password")) + arg_try_empty_password = true; + + else if (!streq(option, "x-initrd.attach")) log_warning("Encountered unknown /etc/crypttab option '%s', ignoring.", option); return 0; @@ -440,6 +474,8 @@ static int attach_tcrypt( struct crypt_device *cd, const char *name, const char *key_file, + const void *key_data, + size_t key_data_size, char **passwords, uint32_t flags) { @@ -453,7 +489,7 @@ static int attach_tcrypt( assert(cd); assert(name); - assert(key_file || (passwords && passwords[0])); + assert(key_file || key_data || !strv_isempty(passwords)); if (arg_pkcs11_uri) /* Ask for a regular password */ @@ -469,22 +505,33 @@ static int attach_tcrypt( if (arg_tcrypt_veracrypt) params.flags |= CRYPT_TCRYPT_VERA_MODES; - if (key_file) { - r = read_one_line_file(key_file, &passphrase); - if (r < 0) { - log_error_errno(r, "Failed to read password file '%s': %m", key_file); - return -EAGAIN; /* log with the actual error, but return EAGAIN */ - } + if (key_data) { + params.passphrase = key_data; + params.passphrase_size = key_data_size; + } else { + if (key_file) { + r = read_one_line_file(key_file, &passphrase); + if (r < 0) { + log_error_errno(r, "Failed to read password file '%s': %m", key_file); + return -EAGAIN; /* log with the actual error, but return EAGAIN */ + } - params.passphrase = passphrase; - } else - params.passphrase = passwords[0]; - params.passphrase_size = strlen(params.passphrase); + params.passphrase = passphrase; + } else + params.passphrase = passwords[0]; + + params.passphrase_size = strlen(params.passphrase); + } r = crypt_load(cd, CRYPT_TCRYPT, ¶ms); if (r < 0) { - if (key_file && r == -EPERM) { - log_error_errno(r, "Failed to activate using password file '%s'. (Key data not correct?)", key_file); + if (r == -EPERM) { + if (key_data) + log_error_errno(r, "Failed to activate using discovered key. (Key not correct?)"); + + if (key_file) + log_error_errno(r, "Failed to activate using password file '%s'. (Key data not correct?)", key_file); + return -EAGAIN; /* log the actual error, but return EAGAIN */ } @@ -502,6 +549,8 @@ static int attach_luks_or_plain( struct crypt_device *cd, const char *name, const char *key_file, + const void *key_data, + size_t key_data_size, char **passwords, uint32_t flags, usec_t until) { @@ -571,7 +620,7 @@ static int attach_luks_or_plain( _cleanup_free_ char *friendly = NULL; size_t decrypted_key_size = 0; - if (!key_file) + if (!key_file && !key_data) 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); @@ -584,8 +633,8 @@ static int attach_luks_or_plain( r = decrypt_pkcs11_key( friendly, arg_pkcs11_uri, - key_file, - arg_keyfile_size, arg_keyfile_offset, + key_file, arg_keyfile_size, arg_keyfile_offset, + key_data, key_data_size, until, &decrypted_key, &decrypted_key_size); if (r >= 0) @@ -620,7 +669,7 @@ static int attach_luks_or_plain( 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); + arg_pkcs11_uri, friendly); /* Let's immediately rescan in case the token appeared in the time we needed * to create and configure the monitor */ @@ -668,6 +717,18 @@ static int attach_luks_or_plain( if (r < 0) return log_error_errno(r, "Failed to activate with PKCS#11 acquired key: %m"); + } else if (key_data) { + if (pass_volume_key) + r = crypt_activate_by_volume_key(cd, name, key_data, key_data_size, flags); + else + r = crypt_activate_by_passphrase(cd, name, arg_key_slot, key_data, key_data_size, flags); + if (r == -EPERM) { + log_error_errno(r, "Failed to activate. (Key incorrect?)"); + return -EAGAIN; /* Log actual error, but return EAGAIN */ + } + if (r < 0) + return log_error_errno(r, "Failed to activate: %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) { @@ -748,6 +809,17 @@ static uint32_t determine_flags(void) { return flags; } +static void remove_and_erasep(const char **p) { + int r; + + if (!*p) + return; + + r = unlinkat_deallocate(AT_FDCWD, *p, UNLINK_ERASE); + if (r < 0 && r != -ENOENT) + log_warning_errno(r, "Unable to erase key file '%s', ignoring: %m", *p); +} + static int run(int argc, char *argv[]) { _cleanup_(crypt_freep) struct crypt_device *cd = NULL; int r; @@ -774,13 +846,19 @@ static int run(int argc, char *argv[]) { unsigned tries; usec_t until; crypt_status_info status; + _cleanup_(remove_and_erasep) const char *destroy_key_file = NULL; const char *key_file = NULL; + _cleanup_(erase_and_freep) void *key_data = NULL; + size_t key_data_size = 0; /* Arguments: systemd-cryptsetup attach VOLUME SOURCE-DEVICE [PASSWORD] [OPTIONS] */ if (argc < 4) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "attach requires at least two arguments."); + if (!filename_is_valid(argv[2])) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Volume name '%s' is not valid.", argv[2]); + if (argc >= 5 && !STR_IN_SET(argv[4], "", "-", "none")) { if (path_is_absolute(argv[4])) key_file = argv[4]; @@ -797,6 +875,24 @@ static int run(int argc, char *argv[]) { /* A delicious drop of snake oil */ (void) mlockall(MCL_FUTURE); + if (!key_file) { + const char *fn; + + /* If a key file is not explicitly specified, search for a key in a well defined + * search path, and load it. */ + + fn = strjoina(argv[2], ".key"); + r = load_key_file(fn, + STRV_MAKE("/etc/cryptsetup-keys.d", "/run/cryptsetup-keys.d"), + 0, 0, /* Note we leave arg_keyfile_offset/arg_keyfile_size as something that only applies to arg_keyfile! */ + &key_data, &key_data_size); + if (r < 0) + return r; + if (r > 0) + log_debug("Automatically discovered key for volume '%s'.", argv[2]); + } else if (arg_keyfile_erase) + destroy_key_file = key_file; /* let's get this baby erased when we leave */ + if (arg_header) { log_debug("LUKS header: %s", arg_header); r = crypt_init(&cd, arg_header); @@ -843,7 +939,7 @@ static int run(int argc, char *argv[]) { } /* Tokens are available in LUKS2 only, but it is ok to call (and fail) with LUKS1. */ - if (!key_file) { + if (!key_file && !key_data) { r = crypt_activate_by_token(cd, argv[2], CRYPT_ANY_TOKEN, NULL, flags); if (r >= 0) { log_debug("Volume %s activated with LUKS token id %i.", argv[2], r); @@ -857,26 +953,53 @@ 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 && !arg_pkcs11_uri) { - r = get_password(argv[2], argv[3], until, tries == 0 && !arg_verify, &passwords); - if (r == -EAGAIN) - continue; - if (r < 0) - return r; + /* When we were able to acquire multiple keys, let's always process them in this order: + * + * 1. A key acquired via PKCS#11 token + * 2. The discovered key: i.e. key_data + key_data_size + * 3. The configured key: i.e. key_file + arg_keyfile_offset + arg_keyfile_size + * 4. The empty password, in case arg_try_empty_password is set + * 5. We enquire the user for a password + */ + + if (!key_file && !key_data && !arg_pkcs11_uri) { + + if (arg_try_empty_password) { + /* Hmm, let's try an empty password now, but only once */ + arg_try_empty_password = false; + + key_data = strdup(""); + if (!key_data) + return log_oom(); + + key_data_size = 0; + } else { + /* Ask the user for a passphrase only as last resort, if we have + * nothing else to check for */ + + r = get_password(argv[2], argv[3], until, tries == 0 && !arg_verify, &passwords); + if (r == -EAGAIN) + continue; + if (r < 0) + return r; + } } if (streq_ptr(arg_type, CRYPT_TCRYPT)) - r = attach_tcrypt(cd, argv[2], key_file, passwords, flags); + r = attach_tcrypt(cd, argv[2], key_file, key_data, key_data_size, passwords, flags); else - r = attach_luks_or_plain(cd, argv[2], key_file, passwords, flags, until); + r = attach_luks_or_plain(cd, argv[2], key_file, key_data, key_data_size, passwords, flags, until); if (r >= 0) break; if (r != -EAGAIN) return r; - /* Passphrase not correct? Let's try again! */ + /* Key not correct? Let's try again! */ + key_file = NULL; - arg_pkcs11_uri = NULL; + key_data = erase_and_free(key_data); + key_data_size = 0; + arg_pkcs11_uri = mfree(arg_pkcs11_uri); } if (arg_tries != 0 && tries >= arg_tries) @@ -884,6 +1007,9 @@ static int run(int argc, char *argv[]) { } else if (streq(argv[1], "detach")) { + if (!filename_is_valid(argv[2])) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Volume name '%s' is not valid.", argv[2]); + r = crypt_init_by_name(&cd, argv[2]); if (r == -ENODEV) { log_info("Volume %s already inactive.", argv[2]); diff --git a/src/test/test-fs-util.c b/src/test/test-fs-util.c index e144551eb7..80c4c209b3 100644 --- a/src/test/test-fs-util.c +++ b/src/test/test-fs-util.c @@ -670,7 +670,7 @@ static void test_unlinkat_deallocate(void) { assert_se(st.st_blocks > 0); assert_se(st.st_nlink == 1); - assert_se(unlinkat_deallocate(AT_FDCWD, p, 0) >= 0); + assert_se(unlinkat_deallocate(AT_FDCWD, p, UNLINK_ERASE) >= 0); assert_se(fstat(fd, &st) >= 0); assert_se(IN_SET(st.st_size, 0, 6)); /* depending on whether hole punching worked the size will be 6