1
0
mirror of https://github.com/systemd/systemd.git synced 2024-12-22 17:35:35 +03:00

cryptenroll: Add support for unlocking through TPM2 enrollments

This commit is contained in:
Gabríel Arthúr Pétursson 2024-01-03 16:10:45 +00:00
parent 97fb1fc4b1
commit 631cf7f004
7 changed files with 176 additions and 7 deletions

View File

@ -222,10 +222,10 @@
<title>Limitations</title>
<para>Note that currently when enrolling a new key of one of the five supported types listed above, it is
required to first provide a passphrase, a recovery key or a FIDO2 token. It's currently not supported to
unlock a device with a TPM2/PKCS#11 key in order to enroll a new TPM2/PKCS#11 key. Thus, if in future key
roll-over is desired it's generally recommended to ensure a passphrase, a recovery key or a FIDO2 token
is always enrolled.</para>
required to first provide a passphrase, a recovery key, a FIDO2 token, or a TPM2 key. It's currently not
supported to unlock a device with a PKCS#11 key in order to enroll a new PKCS#11 key. Thus, if in future
key roll-over is desired it's generally recommended to ensure a passphrase, a recovery key, a FIDO2
token, or a TPM2 key is always enrolled.</para>
<para>Also note that support for enrolling multiple FIDO2 tokens is currently limited. When multiple FIDO2
tokens are enrolled, <command>systemd-cryptseup</command> will perform pre-flight requests to attempt to
@ -310,6 +310,18 @@
<xi:include href="version-info.xml" xpointer="v253"/></listitem>
</varlistentry>
<varlistentry>
<term><option>--unlock-tpm2-device=</option><replaceable>PATH</replaceable></term>
<listitem><para>Use a TPM2 device insteaad of a password/passhprase read from stdin to unlock the
volume. Expects a device node path referring to the TPM2 chip (e.g. <filename>/dev/tpmrm0</filename>).
Alternatively the special value <literal>auto</literal> may be specified, in order to automatically
determine the device node of a currently discovered TPM2 device (of which there must be exactly one).
</para>
<xi:include href="version-info.xml" xpointer="v256"/></listitem>
</varlistentry>
<varlistentry>
<term><option>--pkcs11-token-uri=</option><replaceable>URI</replaceable></term>

View File

@ -52,6 +52,7 @@ _systemd_cryptenroll() {
--password --recovery-key'
[ARG]='--unlock-key-file
--unlock-fido2-device
--unlock-tpm2-device
--pkcs11-token-uri
--fido2-credential-algorithm
--fido2-device
@ -81,6 +82,9 @@ _systemd_cryptenroll() {
--unlock-fido2-device)
comps="auto $(__get_fido2_devices)"
;;
--unlock-tpm2-device)
comps="auto $(__get_tpm2_devices)"
;;
--pkcs11-token-uri)
comps='auto list pkcs11:'
;;

View File

@ -3,10 +3,13 @@
#include "alloc-util.h"
#include "ask-password-api.h"
#include "cryptenroll-tpm2.h"
#include "cryptsetup-tpm2.h"
#include "env-util.h"
#include "errno-util.h"
#include "fileio.h"
#include "hexdecoct.h"
#include "json.h"
#include "log.h"
#include "memory-util.h"
#include "random-util.h"
#include "sha256.h"
@ -129,6 +132,114 @@ static int get_pin(char **ret_pin_str, TPM2Flags *ret_flags) {
return 0;
}
int load_volume_key_tpm2(
struct crypt_device *cd,
const char *cd_node,
const char *device,
void *ret_vk,
size_t *ret_vks) {
_cleanup_(iovec_done_erase) struct iovec decrypted_key = {};
_cleanup_(erase_and_freep) char *passphrase = NULL;
ssize_t passphrase_size;
int r;
assert_se(cd);
assert_se(cd_node);
assert_se(ret_vk);
assert_se(ret_vks);
bool found_some = false;
int token = 0; /* first token to look at */
for (;;) {
_cleanup_(iovec_done) struct iovec pubkey = {}, salt = {}, srk = {}, pcrlock_nv = {};
_cleanup_(iovec_done) struct iovec blob = {}, policy_hash = {};
uint32_t hash_pcr_mask, pubkey_pcr_mask;
uint16_t pcr_bank, primary_alg;
TPM2Flags tpm2_flags;
int keyslot;
r = find_tpm2_auto_data(
cd,
UINT32_MAX,
token,
&hash_pcr_mask,
&pcr_bank,
&pubkey,
&pubkey_pcr_mask,
&primary_alg,
&blob,
&policy_hash,
&salt,
&srk,
&pcrlock_nv,
&tpm2_flags,
&keyslot,
&token);
if (r == -ENXIO)
return log_full_errno(LOG_NOTICE,
SYNTHETIC_ERRNO(EAGAIN),
found_some
? "No TPM2 metadata matching the current system state found in LUKS2 header."
: "No TPM2 metadata enrolled in LUKS2 header.");
if (ERRNO_IS_NEG_NOT_SUPPORTED(r))
/* TPM2 support not compiled in? */
return log_debug_errno(SYNTHETIC_ERRNO(EAGAIN), "TPM2 support not available.");
if (r < 0)
return r;
found_some = true;
r = acquire_tpm2_key(
cd_node,
device,
hash_pcr_mask,
pcr_bank,
&pubkey,
pubkey_pcr_mask,
/* signature_path= */ NULL,
/* pcrlock_path= */ NULL,
primary_alg,
/* key_file= */ NULL, /* key_file_size= */ 0, /* key_file_offset= */ 0, /* no key file */
&blob,
&policy_hash,
&salt,
&srk,
&pcrlock_nv,
tpm2_flags,
/* until= */ 0,
/* headless= */ false,
/* ask_password_flags */ false,
&decrypted_key);
if (IN_SET(r, -EACCES, -ENOLCK))
return log_notice_errno(SYNTHETIC_ERRNO(EAGAIN), "TPM2 PIN unlock failed");
if (r != -EPERM)
break;
token++; /* try a different token next time */
}
if (r < 0)
return log_error_errno(r, "Unlocking via TPM2 device failed: %m");
passphrase_size = base64mem(decrypted_key.iov_base, decrypted_key.iov_len, &passphrase);
if (passphrase_size < 0)
return log_oom();
r = crypt_volume_key_get(
cd,
CRYPT_ANY_SLOT,
ret_vk,
ret_vks,
passphrase,
passphrase_size);
if (r < 0)
return log_error_errno(r, "Unlocking via TPM2 device failed: %m");
return r;
}
int enroll_tpm2(struct crypt_device *cd,
const void *volume_key,
size_t volume_key_size,

View File

@ -8,9 +8,15 @@
#include "tpm2-util.h"
#if HAVE_TPM2
int load_volume_key_tpm2(struct crypt_device *cd, const char *cd_node, const char *device, void *ret_vk, size_t *ret_vks);
int enroll_tpm2(struct crypt_device *cd, const void *volume_key, size_t volume_key_size, const char *device, uint32_t seal_key_handle, const char *device_key, Tpm2PCRValue *hash_pcrs, size_t n_hash_pcrs, const char *pubkey_path, uint32_t pubkey_pcr_mask, const char *signature_path, bool use_pin, const char *pcrlock_path);
#else
static inline int enroll_tpm2(struct crypt_device *cd, const void *volume_key, size_t volume_key_size, const char *device, uint32_t seal_key_handle, const char *device_key, Tpm2PCRValue *hash_pcrs, size_t n_hash_pcrs, const char *pubkey_path, uint32_t pubkey_pcr_mask, const char *signature_path, bool use_pin, const char *pcrlock_path) {
static inline int load_volume_key_tpm2(struct crypt_device *cd, const char *cd_node, const char *device, void *ret_vk, size_t *ret_vks) {
return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
"TPM2 unlocking not supported.");
}
static inline int enroll_tpm2(struct crypt_device *cd, const void *volume_key, size_t volume_key_size, const char *device, uint32_t seal_key_handle, const char *device_key, Tpm2PCRValue *hash_pcrs, size_t n_hash_pcrs, const char *pubkey_path, uint32_t pubkey_pcr_mask, const char *signature_path, bool use_pin, const char *pcrlock_path, int *slot_to_wipe) {
return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
"TPM2 key enrollment not supported.");
}

View File

@ -34,6 +34,7 @@ static EnrollType arg_enroll_type = _ENROLL_TYPE_INVALID;
static char *arg_unlock_keyfile = NULL;
static UnlockType arg_unlock_type = UNLOCK_PASSWORD;
static char *arg_unlock_fido2_device = NULL;
static char *arg_unlock_tpm2_device = NULL;
static char *arg_pkcs11_token_uri = NULL;
static char *arg_fido2_device = NULL;
static char *arg_tpm2_device = NULL;
@ -62,6 +63,7 @@ assert_cc(sizeof(arg_wipe_slots_mask) * 8 >= _ENROLL_TYPE_MAX);
STATIC_DESTRUCTOR_REGISTER(arg_unlock_keyfile, freep);
STATIC_DESTRUCTOR_REGISTER(arg_unlock_fido2_device, freep);
STATIC_DESTRUCTOR_REGISTER(arg_unlock_tpm2_device, freep);
STATIC_DESTRUCTOR_REGISTER(arg_pkcs11_token_uri, freep);
STATIC_DESTRUCTOR_REGISTER(arg_fido2_device, freep);
STATIC_DESTRUCTOR_REGISTER(arg_tpm2_device, freep);
@ -118,6 +120,8 @@ static int help(void) {
" Use a file to unlock the volume\n"
" --unlock-fido2-device=PATH\n"
" Use a FIDO2 device to unlock the volume\n"
" --unlock-tpm2-device=PATH\n"
" Use a TPM2 device to unlock the volume\n"
"\n%3$sSimple Enrollment:%4$s\n"
" --password Enroll a user-supplied password\n"
" --recovery-key Enroll a recovery key\n"
@ -173,6 +177,7 @@ static int parse_argv(int argc, char *argv[]) {
ARG_RECOVERY_KEY,
ARG_UNLOCK_KEYFILE,
ARG_UNLOCK_FIDO2_DEVICE,
ARG_UNLOCK_TPM2_DEVICE,
ARG_PKCS11_TOKEN_URI,
ARG_FIDO2_DEVICE,
ARG_TPM2_DEVICE,
@ -198,6 +203,7 @@ static int parse_argv(int argc, char *argv[]) {
{ "recovery-key", no_argument, NULL, ARG_RECOVERY_KEY },
{ "unlock-key-file", required_argument, NULL, ARG_UNLOCK_KEYFILE },
{ "unlock-fido2-device", required_argument, NULL, ARG_UNLOCK_FIDO2_DEVICE },
{ "unlock-tpm2-device", required_argument, NULL, ARG_UNLOCK_TPM2_DEVICE },
{ "pkcs11-token-uri", required_argument, NULL, ARG_PKCS11_TOKEN_URI },
{ "fido2-credential-algorithm", required_argument, NULL, ARG_FIDO2_CRED_ALG },
{ "fido2-device", required_argument, NULL, ARG_FIDO2_DEVICE },
@ -305,6 +311,26 @@ static int parse_argv(int argc, char *argv[]) {
break;
}
case ARG_UNLOCK_TPM2_DEVICE: {
_cleanup_free_ char *device = NULL;
if (arg_unlock_type != UNLOCK_PASSWORD)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Multiple unlock methods specified at once, refusing.");
assert(!arg_unlock_tpm2_device);
if (!streq(optarg, "auto")) {
device = strdup(optarg);
if (!device)
return log_oom();
}
arg_unlock_type = UNLOCK_TPM2;
arg_unlock_tpm2_device = TAKE_PTR(device);
break;
}
case ARG_PKCS11_TOKEN_URI: {
_cleanup_free_ char *uri = NULL;
@ -667,6 +693,10 @@ static int prepare_luks(
switch (arg_unlock_type) {
case UNLOCK_PASSWORD:
r = load_volume_key_password(cd, arg_node, vk, &vks);
break;
case UNLOCK_KEYFILE:
r = load_volume_key_keyfile(cd, vk, &vks);
break;
@ -675,8 +705,8 @@ static int prepare_luks(
r = load_volume_key_fido2(cd, arg_node, arg_unlock_fido2_device, vk, &vks);
break;
case UNLOCK_PASSWORD:
r = load_volume_key_password(cd, arg_node, vk, &vks);
case UNLOCK_TPM2:
r = load_volume_key_tpm2(cd, arg_node, arg_unlock_tpm2_device, vk, &vks);
break;
default:

View File

@ -17,6 +17,7 @@ typedef enum UnlockType {
UNLOCK_PASSWORD,
UNLOCK_KEYFILE,
UNLOCK_FIDO2,
UNLOCK_TPM2,
_UNLOCK_TYPE_MAX,
_UNLOCK_TYPE_INVALID = -EINVAL,
} UnlockType;

View File

@ -59,6 +59,11 @@ systemd-cryptenroll --fido2-with-user-verification=false "$IMAGE"
systemd-cryptenroll --tpm2-pcrs=8 "$IMAGE"
systemd-cryptenroll --tpm2-pcrs=boot-loader-code+boot-loader-config "$IMAGE"
# Unlocking using TPM2
PASSWORD=foo systemd-cryptenroll --tpm2-device=auto "$IMAGE"
systemd-cryptenroll --unlock-tpm2-device=auto --recovery-key "$IMAGE"
systemd-cryptenroll --unlock-tpm2-device=auto --tpm2-device=auto --wipe-slot=tpm2 "$IMAGE"
(! systemd-cryptenroll --fido2-with-client-pin=false)
(! systemd-cryptenroll --fido2-with-user-presence=f "$IMAGE" /tmp/foo)
(! systemd-cryptenroll --fido2-with-client-pin=1234 "$IMAGE")