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 formnameencrypted-devicepasswordoptions
+ Each line is in the formvolume-nameencrypted-devicekey-fileoptions
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