Merge pull request #15637 from poettering/cryptsetup-literal
a number of cryptsetup fixes and additions
This commit is contained in:
+28
-14
@@ -41,7 +41,7 @@
|
||||
character are ignored. Each of the remaining lines describes one
|
||||
encrypted block device. Fields are delimited by white space.</para>
|
||||
|
||||
<para>Each line is in the form<programlisting><replaceable>name</replaceable> <replaceable>encrypted-device</replaceable> <replaceable>password</replaceable> <replaceable>options</replaceable></programlisting>
|
||||
<para>Each line is in the form<programlisting><replaceable>volume-name</replaceable> <replaceable>encrypted-device</replaceable> <replaceable>key-file</replaceable> <replaceable>options</replaceable></programlisting>
|
||||
The first two fields are mandatory, the remaining two are
|
||||
optional.</para>
|
||||
|
||||
@@ -53,24 +53,20 @@
|
||||
it is opened as a LUKS device; otherwise, it is assumed to be in
|
||||
raw dm-crypt (plain mode) format.</para>
|
||||
|
||||
<para>The first field contains the name of the resulting encrypted
|
||||
block device; the device is set up within
|
||||
<filename>/dev/mapper/</filename>.</para>
|
||||
<para>The first field contains the name of the resulting encrypted volume; its block device is set up
|
||||
below <filename>/dev/mapper/</filename>.</para>
|
||||
|
||||
<para>The second field contains a path to the underlying block
|
||||
device or file, or a specification of a block device via
|
||||
<literal>UUID=</literal> followed by the UUID.</para>
|
||||
|
||||
<para>The third field specifies the encryption password. If the
|
||||
field is not present or the password is set to
|
||||
<literal>none</literal> or <literal>-</literal>, 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, <filename>/dev/urandom</filename>
|
||||
or the hardware device <filename>/dev/hw_random</filename> can be
|
||||
used as the password file; using <filename>/dev/random</filename>
|
||||
may prevent boot completion if the system does not have enough
|
||||
entropy to generate a truly random encryption key.</para>
|
||||
<para>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 <literal>none</literal> or <literal>-</literal>, a key file named after the
|
||||
volume to unlock (i.e. the first column of the line), suffixed with <filename>.key</filename> is
|
||||
automatically loaded from the <filename>/etc/cryptsetup-keys.d/</filename> and
|
||||
<filename>/run/cryptsetup-keys.d/</filename> directories, if present. Otherwise, the password has to be
|
||||
manually entered during system boot. For swap encryption, <filename>/dev/urandom</filename> may be used
|
||||
as key file.</para>
|
||||
|
||||
<para>The fourth field, if present, is a comma-delimited list of
|
||||
options. The following options are recognized:</para>
|
||||
@@ -138,6 +134,15 @@
|
||||
size is then given by the key size.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>keyfile-erase</option></term>
|
||||
|
||||
<listitem><para>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 <filename>/run/</filename>, generated by a service running before
|
||||
activation), and shall be removed after use. Defaults to off.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>key-slot=</option></term>
|
||||
|
||||
@@ -431,6 +436,15 @@
|
||||
before it is used to unlock the LUKS volume.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>try-empty-password=</option></term>
|
||||
|
||||
<listitem><para>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.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>x-systemd.device-timeout=</option></term>
|
||||
|
||||
|
||||
@@ -44,6 +44,32 @@
|
||||
<para>At early boot and when the system manager configuration is reloaded, <filename>/etc/crypttab</filename> is
|
||||
translated into <filename>systemd-cryptsetup@.service</filename> units by
|
||||
<citerefentry><refentrytitle>systemd-cryptsetup-generator</refentrytitle><manvolnum>8</manvolnum></citerefentry>.</para>
|
||||
|
||||
<para>In order to unlock a volume a password or binary key is
|
||||
required. <filename>systemd-cryptsetup@.service</filename> tries to acquire a suitable password or binary
|
||||
key via the following mechanisms, tried in order:</para>
|
||||
|
||||
<orderedlist>
|
||||
<listitem><para>If a key file is explicitly configured (via the third column in
|
||||
<filename>/etc/crypttab</filename>), a key read from it is used. If a PKCS#11 token is configured
|
||||
(using the <varname>pkcs11-uri=</varname> option) the key is decrypted before use.</para></listitem>
|
||||
|
||||
<listitem><para>If no key file is configured explicitly this way, a key file is automatically loaded
|
||||
from <filename>/etc/cryptsetup-keys.d/<replaceable>volume</replaceable>.key</filename> and
|
||||
<filename>/run/cryptsetup-keys.d/<replaceable>volume</replaceable>.key</filename>, if present. Here
|
||||
too, if a PKCS#11 token is configured, any key found this way is decrypted before
|
||||
use.</para></listitem>
|
||||
|
||||
<listitem><para>If the <varname>try-empty-password</varname> option is specified it is then attempted
|
||||
to unlock the volume with an empty password.</para></listitem>
|
||||
|
||||
<listitem><para>The kernel keyring is then checked for a suitable cached password from previous
|
||||
attempts.</para></listitem>
|
||||
|
||||
<listitem><para>Finally, the user is queried for a password, possibly multiple times.</para></listitem>
|
||||
</orderedlist>
|
||||
|
||||
<para>If no suitable key may be acquired via any of the mechanisms describes above, volume activation fails.</para>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
|
||||
@@ -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'
|
||||
|
||||
+3
-1
@@ -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
|
||||
|
||||
+45
-4
@@ -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
|
||||
|
||||
+7
-1
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1+ */
|
||||
|
||||
#include <unistd.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1+ */
|
||||
#pragma once
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
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);
|
||||
+162
-36
@@ -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]);
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user