diff --git a/man/crypttab.xml b/man/crypttab.xml
index 0469d365ef..2a54c81595 100644
--- a/man/crypttab.xml
+++ b/man/crypttab.xml
@@ -685,6 +685,21 @@
when TPM2 enrollment metadata is not available.
+
+
+
+ Takes an absolute path to a TPM2 PCR JSON signature file, as produced by the
+ systemd-measure1
+ tool. This permits locking LUKS2 volumes to any PCR values for which a valid signature matching a
+ public key specified at key enrollment time can be provided. See
+ systemd-cryptenroll1
+ for details on enrolling TPM2 PCR public keys. If this option is not specified but it is attempted to
+ unlock a LUKS2 volume with a signed TPM2 PCR enrollment a suitable signature file
+ tpm2-pcr-signature.json is searched for in /etc/systemd/,
+ /run/systemd/, /usr/lib/systemd/ (in this
+ order).
+
+
diff --git a/man/systemd-creds.xml b/man/systemd-creds.xml
index 7592961f63..d9b30a7e96 100644
--- a/man/systemd-creds.xml
+++ b/man/systemd-creds.xml
@@ -333,6 +333,40 @@
systemd-cryptenroll1.
+
+ PATH
+ PCR
+
+ Configures a TPM2 signed PCR policy to bind encryption to, for use with the
+ encrypt command. The option accepts a path to
+ a PEM encoded RSA public key, to bind the encryption to. If this is not specified explicitly, but a
+ file tpm2-pcr-public-key.pem exists in one of the directories
+ /etc/systemd/, /run/systemd/,
+ /usr/lib/systemd/ (searched in this order), it is automatically used. The
+ option takes a list of TPM2 PCR indexes to bind to (same
+ syntax as described above). If not specified defaults to 11 (i.e. this
+ binds the policy to any unified kernel image for which a PCR signature can be provided).
+
+ Note the difference between and
+ : the former binds decryption to the current, specific PCR
+ values; the latter binds decryption to any set of PCR values for which a signature by the specified
+ public key can be provided. The latter is hence more useful in scenarios where software updates shall
+ be possible without losing access to all previously encrypted secrets.
+
+
+
+ PATH
+
+ Takes a path to a TPM2 PCR signature file as generated by the
+ systemd-measure1
+ tool and that may be used to allow the decrypt command to decrypt credentials that
+ are bound to specific signed PCR values. If this this is not specified explicitly, and a credential
+ with a signed PCR policy is attempted to be decrypted, a suitable signature file
+ tpm2-pcr-signature.json is searched for in /etc/systemd/,
+ /run/systemd/, /usr/lib/systemd/ (in this order) and
+ used.
+
+
@@ -413,7 +447,8 @@ SetCredentialEncrypted=mysql-password: \
See Also
systemd1,
- systemd.exec5
+ systemd.exec5,
+ systemd-measure1
diff --git a/man/systemd-cryptenroll.xml b/man/systemd-cryptenroll.xml
index afde0fad7e..889dd39b03 100644
--- a/man/systemd-cryptenroll.xml
+++ b/man/systemd-cryptenroll.xml
@@ -347,16 +347,52 @@
to no. Despite being called PIN, any character can be used, not just numbers.
- Note that incorrect PIN entry when unlocking increments the
- TPM dictionary attack lockout mechanism, and may lock out users for a prolonged time, depending on
- its configuration. The lockout mechanism is a global property of the TPM,
- systemd-cryptenroll does not control or configure the lockout mechanism. You may
- use tpm2-tss tools to inspect or configure the dictionary attack lockout, with
- tpm2_getcap1 and
- tpm2_dictionarylockout1
+ Note that incorrect PIN entry when unlocking increments the TPM dictionary attack lockout
+ mechanism, and may lock out users for a prolonged time, depending on its configuration. The lockout
+ mechanism is a global property of the TPM, systemd-cryptenroll does not control or
+ configure the lockout mechanism. You may use tpm2-tss tools to inspect or configure the dictionary
+ attack lockout, with tpm2_getcap1
+ and tpm2_dictionarylockout1
commands, respectively.
+
+ PATH
+ PCR
+ PATH
+
+ Configures a TPM2 signed PCR policy to bind encryption to. The
+ option accepts a path to a PEM encoded RSA public key, to bind
+ the encryption to. If this is not specified explicitly, but a file
+ tpm2-pcr-public-key.pem exists in one of the directories
+ /etc/systemd/, /run/systemd/,
+ /usr/lib/systemd/ (searched in this order), it is automatically used. The
+ option takes a list of TPM2 PCR indexes to bind to (same
+ syntax as described above). If not specified defaults to 11 (i.e. this
+ binds the policy to any unified kernel image for which a PCR signature can be provided).
+
+ Note the difference between and
+ : the former binds decryption to the current, specific PCR
+ values; the latter binds decryption to any set of PCR values for which a signature by the specified
+ public key can be provided. The latter is hence more useful in scenarios where software updates shell
+ be possible without losing access to all previously encrypted LUKS2 volumes.
+
+ The option takes a path to a TPM2 PCR signature file
+ as generated by the
+ systemd-measure1
+ tool. If this this is not specified explicitly a suitable signature file
+ tpm2-pcr-signature.json is searched for in /etc/systemd/,
+ /run/systemd/, /usr/lib/systemd/ (in this order) and
+ used. If a signature file is specified or found it is used to verify if the volume can be unlocked
+ with it given the current PCR state, before the new slot is written to disk. This is intended as
+ safety net to ensure that access to a volume is not lost if a public key is enrolled for which no
+ valid signature for the current PCR state is available. If the supplied signature does not unlock the
+ current PCR state and public key combination, no slot is enrolled and the operation will fail. If no
+ signature file is specified or found no such safety verification is done.
+
+
SLOT
@@ -411,7 +447,8 @@
systemd1,
systemd-cryptsetup@.service8,
crypttab5,
- cryptsetup8
+ cryptsetup8,
+ systemd-measure1
diff --git a/man/systemd-measure.xml b/man/systemd-measure.xml
index 65cb2e503e..0fc0d0e87d 100644
--- a/man/systemd-measure.xml
+++ b/man/systemd-measure.xml
@@ -17,7 +17,7 @@
systemd-measure
- Pre-calculate expected TPM2 PCR values for booted unified kernel images
+ Pre-calculate and sign expected TPM2 PCR values for booted unified kernel images
@@ -32,15 +32,17 @@
Note: this command is experimental for now. While it is likely to become a regular component of
systemd, it might still change in behaviour and interface.
- systemd-measure is a tool that may be used to pre-calculate the expected TPM2
- PCR 11 values that should be seen when a unified Linux kernel image based on
+ systemd-measure is a tool that may be used to pre-calculate and sign the
+ expected TPM2 PCR 11 values that should be seen when a unified Linux kernel image based on
systemd-stub7 is
booted up. It accepts paths to the ELF kernel image file, initial ram disk image file, devicetree file,
kernel command line file,
os-release5 file, and
boot splash file that make up the unified kernel image, and determines the PCR values expected to be in
place after booting the image. Calculation starts with a zero-initialized PCR 11, and is executed in a
- fashion compatible with what systemd-stub does at boot.
+ fashion compatible with what systemd-stub does at boot. The result may optionally be
+ signed cryptographically, to allow TPM2 policies that can only be unlocked if a certain set of kernels is
+ booted, for which such a PCR signature can be provided.
@@ -61,11 +63,30 @@
calculate
- Pre-calculate the expected value seen in PCR register 11 after boot-up of a unified
+ Pre-calculate the expected values seen in PCR register 11 after boot-up of a unified
kernel image consisting of the components specified with ,
, , ,
, , see below. Only is
- mandatory.
+ mandatory. (Alternatively, specify to use the current values of PCR
+ register 11 instead.)
+
+
+
+ sign
+
+ As with the calculate command, pre-calculate the expected value
+ seen in TPM2 PCR register 11 after boot-up of a unified kernel image. Then, cryptographically sign
+ the resulting values with the private/public key pair (RSA) configured via
+ and . This will write a JSON object to
+ standard output that contains signatures for all specified PCR banks (see
+ ) below, which may be used to unlock encrypted credentials (see
+ systemd-creds1) or
+ LUKS volumes (see
+ systemd-cryptsetup@.service8). This
+ allows binding secrets to a set of kernels for which such PCR 11 signatures can be provided.
+
+ Note that a TPM2 device must be available for this signing to take place, even though the
+ result is not tied to any TPM2 device or its state.
@@ -84,29 +105,47 @@
- When used with the calculate verb, configures the files to read
- the unified kernel image components from. Each option corresponds with the equally named section in
- the unified kernel PE file. The switch expects the path to the ELF kernel
- file that the unified PE kernel will wrap. All switches except are
- optional. Each option may be used at most once.
+ When used with the calculate or sign verb,
+ configures the files to read the unified kernel image components from. Each option corresponds with
+ the equally named section in the unified kernel PE file. The switch expects
+ the path to the ELF kernel file that the unified PE kernel will wrap. All switches except
+ are optional. Each option may be used at most once.
- When used with the calculate verb, takes the PCR 11 values
- currently in effect for the system (which should typically reflect the hashes of the currently booted
- kernel). This can be used in place of and the other switches listed
- above.
+ When used with the calculate or sign verb,
+ takes the PCR 11 values currently in effect for the system (which should typically reflect the hashes
+ of the currently booted kernel). This can be used in place of and the other
+ switches listed above.
Controls the PCR banks to pre-calculate the PCR values for – in case
- calculate is invoked –, or the banks to show in the status
- output. May be used more then once to specify multiple banks. If not specified, defaults to the four
- banks sha1, sha256, sha384,
- sha512.
+ calculate or sign is invoked –, or the banks to show in the
+ status output. May be used more then once to specify multiple banks. If not
+ specified, defaults to the four banks sha1, sha256,
+ sha384, sha512.
+
+
+
+
+
+
+ These switches take paths to a pair of PEM encoded RSA key files, for use with
+ the sign command.
+
+
+
+ PATH
+
+ Controls which TPM2 device to use. Expects a device node path referring to the TPM2
+ chip (e.g. /dev/tpmrm0). Alternatively the special value auto
+ may be specified, in order to automatically determine the device node of a suitable TPM2 device (of
+ which there must be exactly one). The special value list may be used to enumerate
+ all suitable TPM2 devices currently discovered.
@@ -133,7 +172,7 @@
foo.efi
# systemd-measure calculate \
--linux=vmlinux \
- --osrel=os-release \
+ --osrel=os-release.txt \
--cmdline=cmdline.txt \
--initrd=initrd.cpio \
--splash=splash.bmp \
@@ -144,6 +183,41 @@
11:sha512=8e79acd3ddbbc8282e98091849c3530f996303c8ac8e87a3b2378b71c8b3a6e86d5c4f41ecea9e1517090c3e8ec0c714821032038f525f744960bcd082d937da
+
+
+ Generate a private/public key pair, and a unified kernel image, and a TPM PCR 11 signature for it
+
+ # openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out tpm2-pcr-private.pem
+# openssl rsa -pubout -in tpm2-pcr-private.pem -out tpm2-pcr-public.pem
+# objcopy \
+ --add-section .linux=vmlinux --change-section-vma .linux=0x2000000 \
+ --add-section .osrel=os-release.txt --change-section-vma .osrel=0x20000 \
+ --add-section .cmdline=cmdline.txt --change-section-vma .cmdline=0x30000 \
+ --add-section .initrd=initrd.cpio --change-section-vma .initrd=0x3000000 \
+ --add-section .splash=splash.bmp --change-section-vma .splash=0x100000 \
+ --add-section .dtb=devicetree.dtb --change-section-vma .dtb=0x40000 \
+ /usr/lib/systemd/boot/efi/linuxx64.efi.stub \
+ foo.efi
+# systemd-measure sign \
+ --linux=vmlinux \
+ --osrel=os-release.txt \
+ --cmdline=cmdline.txt \
+ --initrd=initrd.cpio \
+ --splash=splash.bmp \
+ --dtb=devicetree.dtb \
+ --bank=sha1 \
+ --bank=sha256 \
+ --private-key=tpm2-pcr-private.pem \
+ --public-key=tpm2-pcr-public.pem > tpm2-pcr-signature.json
+
+ Later on, enroll the signed PCR policy on a LUKS volume:
+
+ # systemd-cryptenroll --tpm2-device=auto --tpm2-public-key=tpm2-pcr-public.pem --tpm2-signature=tpm2-pcr-signature.json /dev/sda5
+
+ And then unlock the device with the signature:
+
+ # /usr/lib/systemd/systemd-cryptsetup attach myvolume /dev/sda5 - tpm2-device=auto,tpm2-signature=/path/to/tpm2-pcr-signature.json
+
@@ -157,7 +231,9 @@
systemd1,
systemd-stub7,
- objcopy1
+ objcopy1,
+ systemd-creds1,
+ systemd-cryptsetup@.service8
diff --git a/man/systemd-repart.xml b/man/systemd-repart.xml
index c34244d14a..475aeec212 100644
--- a/man/systemd-repart.xml
+++ b/man/systemd-repart.xml
@@ -323,6 +323,15 @@
and have the same effect on partitions where TPM2 enrollment is requested.
+
+ PATH
+ PCR
+
+ Configures a TPM2 signed PCR policy to bind encryption to. See
+ systemd-cryptenroll1
+ for details on these two options.
+
+
diff --git a/src/boot/measure.c b/src/boot/measure.c
index 824a270ff2..e8404026cb 100644
--- a/src/boot/measure.c
+++ b/src/boot/measure.c
@@ -14,6 +14,7 @@
#include "parse-argument.h"
#include "parse-util.h"
#include "pretty-print.h"
+#include "sha256.h"
#include "terminal-util.h"
#include "tpm-pcr.h"
#include "tpm2-util.h"
@@ -24,11 +25,17 @@
static char *arg_sections[_UNIFIED_SECTION_MAX] = {};
static char **arg_banks = NULL;
-static JsonFormatFlags arg_json_format_flags = JSON_FORMAT_OFF;
+static char *arg_tpm2_device = NULL;
+static char *arg_private_key = NULL;
+static char *arg_public_key = NULL;
+static JsonFormatFlags arg_json_format_flags = JSON_FORMAT_PRETTY_AUTO|JSON_FORMAT_COLOR_AUTO|JSON_FORMAT_OFF;
static PagerFlags arg_pager_flags = 0;
static bool arg_current = false;
STATIC_DESTRUCTOR_REGISTER(arg_banks, strv_freep);
+STATIC_DESTRUCTOR_REGISTER(arg_tpm2_device, freep);
+STATIC_DESTRUCTOR_REGISTER(arg_private_key, freep);
+STATIC_DESTRUCTOR_REGISTER(arg_public_key, freep);
static inline void free_sections(char*(*sections)[_UNIFIED_SECTION_MAX]) {
for (UnifiedSection c = 0; c < _UNIFIED_SECTION_MAX; c++)
@@ -46,10 +53,11 @@ static int help(int argc, char *argv[], void *userdata) {
return log_oom();
printf("%1$s [OPTIONS...] COMMAND ...\n"
- "\n%5$sPre-calculate PCR hash for kernel image.%6$s\n"
+ "\n%5$sPre-calculate and sign PCR hash for a unified kernel image.%6$s\n"
"\n%3$sCommands:%4$s\n"
- " status Show current PCR values\n"
- " calculate Calculate expected PCR values\n"
+ " status Show current PCR values\n"
+ " calculate Calculate expected PCR values\n"
+ " sign Calculate and sign expected PCR values\n"
"\n%3$sOptions:%4$s\n"
" -h --help Show this help\n"
" --version Print version\n"
@@ -62,6 +70,9 @@ static int help(int argc, char *argv[], void *userdata) {
" --dtb=PATH Path to Devicetree file\n"
" -c --current Use current PCR values\n"
" --bank=DIGEST Select TPM bank (SHA1, SHA256)\n"
+ " --tpm2-device=PATH Use specified TPM2 device\n"
+ " --private-key=KEY Private key (PEM) to sign with\n"
+ " --public-key=KEY Public key (PEM) to validate against\n"
" --json=MODE Output as JSON\n"
" -j Same as --json=pretty on tty, --json=short otherwise\n"
"\nSee the %2$s for details.\n",
@@ -88,6 +99,9 @@ static int parse_argv(int argc, char *argv[]) {
_ARG_SECTION_LAST,
ARG_DTB = _ARG_SECTION_LAST,
ARG_BANK,
+ ARG_PRIVATE_KEY,
+ ARG_PUBLIC_KEY,
+ ARG_TPM2_DEVICE,
ARG_JSON,
};
@@ -103,6 +117,9 @@ static int parse_argv(int argc, char *argv[]) {
{ "dtb", required_argument, NULL, ARG_DTB },
{ "current", no_argument, NULL, 'c' },
{ "bank", required_argument, NULL, ARG_BANK },
+ { "tpm2-device", required_argument, NULL, ARG_TPM2_DEVICE },
+ { "private-key", required_argument, NULL, ARG_PRIVATE_KEY },
+ { "public-key", required_argument, NULL, ARG_PUBLIC_KEY },
{ "json", required_argument, NULL, ARG_JSON },
{}
};
@@ -155,6 +172,36 @@ static int parse_argv(int argc, char *argv[]) {
break;
}
+ case ARG_PRIVATE_KEY:
+ r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_private_key);
+ if (r < 0)
+ return r;
+
+ break;
+
+ case ARG_PUBLIC_KEY:
+ r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_public_key);
+ if (r < 0)
+ return r;
+
+ break;
+
+ case ARG_TPM2_DEVICE: {
+ _cleanup_free_ char *device = NULL;
+
+ if (streq(optarg, "list"))
+ return tpm2_list_devices();
+
+ if (!streq(optarg, "auto")) {
+ device = strdup(optarg);
+ if (!device)
+ return log_oom();
+ }
+
+ free_and_replace(arg_tpm2_device, device);
+ break;
+ }
+
case 'j':
arg_json_format_flags = JSON_FORMAT_PRETTY_AUTO|JSON_FORMAT_COLOR_AUTO;
break;
@@ -379,14 +426,9 @@ static int measure_pcr(PcrState *pcr_states, size_t n) {
return 0;
}
-static int verb_calculate(int argc, char *argv[], void *userdata) {
+static int pcr_states_allocate(PcrState **ret) {
_cleanup_(pcr_state_free_all) PcrState *pcr_states = NULL;
- _cleanup_(json_variant_unrefp) JsonVariant *w = NULL;
size_t n = 0;
- int r;
-
- if (!arg_sections[UNIFIED_SECTION_LINUX] && !arg_current)
- return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Either --linux= or --current must be specified, refusing.");
pcr_states = new0(PcrState, strv_length(arg_banks) + 1);
if (!pcr_states)
@@ -421,6 +463,25 @@ static int verb_calculate(int argc, char *argv[], void *userdata) {
};
}
+ *ret = TAKE_PTR(pcr_states);
+ return (int) n;
+}
+
+static int verb_calculate(int argc, char *argv[], void *userdata) {
+ _cleanup_(json_variant_unrefp) JsonVariant *w = NULL;
+ _cleanup_(pcr_state_free_all) PcrState *pcr_states = NULL;
+ size_t n;
+ int r;
+
+ if (!arg_sections[UNIFIED_SECTION_LINUX] && !arg_current)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Either --linux= or --current must be specified, refusing.");
+
+ r = pcr_states_allocate(&pcr_states);
+ if (r < 0)
+ return r;
+
+ n = (size_t) r;
+
r = measure_pcr(pcr_states, n);
if (r < 0)
return r;
@@ -466,6 +527,235 @@ static int verb_calculate(int argc, char *argv[], void *userdata) {
return 0;
}
+static TPM2_ALG_ID convert_evp_md_name_to_tpm2_alg(const EVP_MD *md) {
+ const char *mdname;
+
+ mdname = EVP_MD_name(md);
+ if (strcaseeq(mdname, "sha1"))
+ return TPM2_ALG_SHA1;
+ if (strcaseeq(mdname, "sha256"))
+ return TPM2_ALG_SHA256;
+ if (strcaseeq(mdname, "sha384"))
+ return TPM2_ALG_SHA384;
+ if (strcaseeq(mdname, "sha512"))
+ return TPM2_ALG_SHA512;
+
+ return TPM2_ALG_ERROR;
+}
+
+static int verb_sign(int argc, char *argv[], void *userdata) {
+ _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
+ _cleanup_(pcr_state_free_all) PcrState *pcr_states = NULL;
+ _cleanup_(EVP_PKEY_freep) EVP_PKEY *privkey = NULL, *pubkey = NULL;
+ _cleanup_(tpm2_context_destroy) struct tpm2_context c = {};
+ _cleanup_fclose_ FILE *privkeyf = NULL , *pubkeyf = NULL;
+ ESYS_TR session_handle = ESYS_TR_NONE;
+ TSS2_RC rc;
+ size_t n;
+ int r;
+
+ if (!arg_sections[UNIFIED_SECTION_LINUX] && !arg_current)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Either --linux= or --current must be specified, refusing.");
+
+ if (!arg_private_key)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No private key specified, use --private-key=.");
+ if (!arg_public_key)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No public key specified, use --public-key=.");
+
+ /* When signing we only support JSON output */
+ arg_json_format_flags &= ~JSON_FORMAT_OFF;
+
+ privkeyf = fopen(arg_private_key, "re");
+ if (!privkeyf)
+ return log_error_errno(errno, "Failed to open private key file '%s': %m", arg_private_key);
+
+ pubkeyf = fopen(arg_public_key, "re");
+ if (!pubkeyf)
+ return log_error_errno(errno, "Failed to open public key file '%s': %m", arg_public_key);
+
+ privkey = PEM_read_PrivateKey(privkeyf, NULL, NULL, NULL);
+ if (!privkey)
+ return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to parse private key '%s'.", arg_private_key);
+
+ pubkey = PEM_read_PUBKEY(pubkeyf, NULL, NULL, NULL);
+ if (!pubkey)
+ return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to parse public key '%s'.", arg_public_key);
+
+ r = pcr_states_allocate(&pcr_states);
+ if (r < 0)
+ return r;
+
+ n = (size_t) r;
+
+ r = measure_pcr(pcr_states, n);
+ if (r < 0)
+ return r;
+
+ r = dlopen_tpm2();
+ if (r < 0)
+ return r;
+
+ r = tpm2_context_init(arg_tpm2_device, &c);
+ if (r < 0)
+ return r;
+
+ for (size_t i = 0; i < n; i++) {
+ static const TPMT_SYM_DEF symmetric = {
+ .algorithm = TPM2_ALG_AES,
+ .keyBits.aes = 128,
+ .mode.aes = TPM2_ALG_CFB,
+ };
+ PcrState *p = pcr_states + i;
+
+ rc = sym_Esys_StartAuthSession(
+ c.esys_context,
+ ESYS_TR_NONE,
+ ESYS_TR_NONE,
+ ESYS_TR_NONE,
+ ESYS_TR_NONE,
+ ESYS_TR_NONE,
+ NULL,
+ TPM2_SE_TRIAL,
+ &symmetric,
+ TPM2_ALG_SHA256,
+ &session_handle);
+ if (rc != TSS2_RC_SUCCESS) {
+ r = log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
+ "Failed to open session in TPM: %s", sym_Tss2_RC_Decode(rc));
+ goto finish;
+ }
+
+ /* Generate a single hash value from the PCRs included in our policy. Given that that's
+ * exactly one, the calculation is trivial. */
+ TPM2B_DIGEST intermediate_digest = {
+ .size = SHA256_DIGEST_SIZE,
+ };
+ assert(sizeof(intermediate_digest.buffer) >= SHA256_DIGEST_SIZE);
+ sha256_direct(p->value, p->value_size, intermediate_digest.buffer);
+
+ TPM2_ALG_ID tpmalg = convert_evp_md_name_to_tpm2_alg(p->md);
+ if (tpmalg == TPM2_ALG_ERROR) {
+ r = log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Unsupported PCR bank");
+ goto finish;
+ }
+
+ TPML_PCR_SELECTION pcr_selection;
+ tpm2_pcr_mask_to_selection(1 << TPM_PCR_INDEX_KERNEL_IMAGE, tpmalg, &pcr_selection);
+
+ rc = sym_Esys_PolicyPCR(
+ c.esys_context,
+ session_handle,
+ ESYS_TR_NONE,
+ ESYS_TR_NONE,
+ ESYS_TR_NONE,
+ &intermediate_digest,
+ &pcr_selection);
+ if (rc != TSS2_RC_SUCCESS) {
+ r = log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
+ "Failed to push PCR policy into TPM: %s", sym_Tss2_RC_Decode(rc));
+ goto finish;
+ }
+
+ _cleanup_(Esys_Freep) TPM2B_DIGEST *pcr_policy_digest = NULL;
+ rc = sym_Esys_PolicyGetDigest(
+ c.esys_context,
+ session_handle,
+ ESYS_TR_NONE,
+ ESYS_TR_NONE,
+ ESYS_TR_NONE,
+ &pcr_policy_digest);
+ if (rc != TSS2_RC_SUCCESS) {
+ r = log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
+ "Failed to get policy digest from TPM: %s", sym_Tss2_RC_Decode(rc));
+ goto finish;
+ }
+
+ session_handle = tpm2_flush_context_verbose(c.esys_context, session_handle);
+
+ _cleanup_(EVP_MD_CTX_freep) EVP_MD_CTX* mdctx = NULL;
+ mdctx = EVP_MD_CTX_new();
+ if (!mdctx) {
+ r = log_oom();
+ goto finish;
+ }
+
+ if (EVP_DigestSignInit(mdctx, NULL, p->md, NULL, privkey) != 1) {
+ r = log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
+ "Failed to initialize signature context.");
+ goto finish;
+ }
+
+ if (EVP_DigestSignUpdate(mdctx, pcr_policy_digest->buffer, pcr_policy_digest->size) != 1) {
+ r = log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
+ "Failed to sign data.");
+ goto finish;
+ }
+
+ size_t ss;
+ if (EVP_DigestSignFinal(mdctx, NULL, &ss) != 1) {
+ r = log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
+ "Failed to finalize signature");
+ goto finish;
+ }
+
+ _cleanup_free_ void *sig = malloc(ss);
+ if (!ss) {
+ r = log_oom();
+ goto finish;
+ }
+
+ if (EVP_DigestSignFinal(mdctx, sig, &ss) != 1) {
+ r = log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
+ "Failed to acquire signature data");
+ goto finish;
+ }
+
+ _cleanup_free_ void *pubkey_fp = NULL;
+ size_t pubkey_fp_size = 0;
+ r = pubkey_fingerprint(pubkey, EVP_sha256(), &pubkey_fp, &pubkey_fp_size);
+ if (r < 0)
+ goto finish;
+
+ _cleanup_(json_variant_unrefp) JsonVariant *bv = NULL, *a = NULL;
+
+ r = tpm2_make_pcr_json_array(UINT64_C(1) << TPM_PCR_INDEX_KERNEL_IMAGE, &a);
+ if (r < 0) {
+ log_error_errno(r, "Failed to build JSON PCR mask array: %m");
+ goto finish;
+ }
+
+ r = json_build(&bv, JSON_BUILD_ARRAY(
+ JSON_BUILD_OBJECT(
+ JSON_BUILD_PAIR("pcrs", JSON_BUILD_VARIANT(a)), /* PCR mask */
+ JSON_BUILD_PAIR("pkfp", JSON_BUILD_HEX(pubkey_fp, pubkey_fp_size)), /* SHA256 fingerprint of public key (DER) used for the signature */
+ JSON_BUILD_PAIR("pol", JSON_BUILD_HEX(pcr_policy_digest->buffer, pcr_policy_digest->size)), /* TPM2 policy hash that is signed */
+ JSON_BUILD_PAIR("sig", JSON_BUILD_BASE64(sig, ss))))); /* signature data */
+ if (r < 0) {
+ log_error_errno(r, "Failed to build JSON object: %m");
+ goto finish;
+ }
+
+ r = json_variant_set_field(&v, p->bank, bv);
+ if (r < 0) {
+ log_error_errno(r, "Failed to add JSON field: %m");
+ goto finish;
+ }
+ }
+
+ if (!v)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unable to find a single working PCR bank.");
+
+ if (arg_json_format_flags & (JSON_FORMAT_PRETTY|JSON_FORMAT_PRETTY_AUTO))
+ pager_open(arg_pager_flags);
+
+ json_variant_dump(v, arg_json_format_flags, stdout, NULL);
+ r = 0;
+
+finish:
+ session_handle = tpm2_flush_context_verbose(c.esys_context, session_handle);
+ return r;
+}
+
static int compare_reported_pcr_nr(uint32_t pcr, const char *varname, const char *description) {
_cleanup_free_ char *s = NULL;
uint32_t v;
@@ -643,6 +933,7 @@ static int measure_main(int argc, char *argv[]) {
{ "help", VERB_ANY, VERB_ANY, 0, help },
{ "status", VERB_ANY, 1, VERB_DEFAULT, verb_status },
{ "calculate", VERB_ANY, 1, 0, verb_calculate },
+ { "sign", VERB_ANY, 1, 0, verb_sign },
{}
};
diff --git a/src/core/execute.c b/src/core/execute.c
index 0ce18159bd..6474c97e80 100644
--- a/src/core/execute.c
+++ b/src/core/execute.c
@@ -2746,7 +2746,7 @@ static int load_credential(
_cleanup_free_ void *plaintext = NULL;
size_t plaintext_size = 0;
- r = decrypt_credential_and_warn(id, now(CLOCK_REALTIME), NULL, data, size, &plaintext, &plaintext_size);
+ r = decrypt_credential_and_warn(id, now(CLOCK_REALTIME), NULL, NULL, data, size, &plaintext, &plaintext_size);
if (r < 0)
return r;
@@ -2920,7 +2920,7 @@ static int acquire_credentials(
return log_debug_errno(errno, "Failed to test if credential %s exists: %m", sc->id);
if (sc->encrypted) {
- r = decrypt_credential_and_warn(sc->id, now(CLOCK_REALTIME), NULL, sc->data, sc->size, &plaintext, &size);
+ r = decrypt_credential_and_warn(sc->id, now(CLOCK_REALTIME), NULL, NULL, sc->data, sc->size, &plaintext, &size);
if (r < 0)
return r;
diff --git a/src/creds/creds.c b/src/creds/creds.c
index c067c886db..5586fd776a 100644
--- a/src/creds/creds.c
+++ b/src/creds/creds.c
@@ -21,6 +21,7 @@
#include "stat-util.h"
#include "string-table.h"
#include "terminal-util.h"
+#include "tpm-pcr.h"
#include "tpm2-util.h"
#include "verbs.h"
@@ -43,6 +44,9 @@ static int arg_newline = -1;
static sd_id128_t arg_with_key = _CRED_AUTO;
static const char *arg_tpm2_device = NULL;
static uint32_t arg_tpm2_pcr_mask = UINT32_MAX;
+static char *arg_tpm2_public_key = NULL;
+static uint32_t arg_tpm2_public_key_pcr_mask = UINT32_MAX;
+static char *arg_tpm2_signature = NULL;
static const char *arg_name = NULL;
static bool arg_name_any = false;
static usec_t arg_timestamp = USEC_INFINITY;
@@ -50,6 +54,9 @@ static usec_t arg_not_after = USEC_INFINITY;
static bool arg_pretty = false;
static bool arg_quiet = false;
+STATIC_DESTRUCTOR_REGISTER(arg_tpm2_public_key, freep);
+STATIC_DESTRUCTOR_REGISTER(arg_tpm2_signature, freep);
+
static const char* transcode_mode_table[_TRANSCODE_MAX] = {
[TRANSCODE_OFF] = "off",
[TRANSCODE_BASE64] = "base64",
@@ -418,6 +425,7 @@ static int verb_cat(int argc, char **argv, void *userdata) {
*cn,
timestamp,
arg_tpm2_device,
+ arg_tpm2_signature,
data, size,
&plaintext, &plaintext_size);
if (r < 0)
@@ -490,6 +498,8 @@ static int verb_encrypt(int argc, char **argv, void *userdata) {
arg_not_after,
arg_tpm2_device,
arg_tpm2_pcr_mask,
+ arg_tpm2_public_key,
+ arg_tpm2_public_key_pcr_mask,
plaintext, plaintext_size,
&output, &output_size);
if (r < 0)
@@ -577,6 +587,7 @@ static int verb_decrypt(int argc, char **argv, void *userdata) {
name,
timestamp,
arg_tpm2_device,
+ arg_tpm2_signature,
input, input_size,
&plaintext, &plaintext_size);
if (r < 0)
@@ -682,7 +693,13 @@ static int verb_help(int argc, char **argv, void *userdata) {
" --tpm2-device=PATH\n"
" Pick TPM2 device\n"
" --tpm2-pcrs=PCR1+PCR2+PCR3+…\n"
- " Specify TPM2 PCRs to seal against\n"
+ " Specify TPM2 PCRs to seal against (fixed hash)\n"
+ " --tpm2-public-key=PATH\n"
+ " Specify PEM certificate to seal against\n"
+ " --tpm2-public-key-pcrs=PCR1+PCR2+PCR3+…\n"
+ " Specify TPM2 PCRs to seal against (public key)\n"
+ " --tpm2-signature=PATH\n"
+ " Specify signature for public key PCR policy\n"
" -q --quiet Suppress output for 'has-tpm2' verb\n"
"\nSee the %2$s for details.\n"
, program_invocation_short_name
@@ -707,28 +724,34 @@ static int parse_argv(int argc, char *argv[]) {
ARG_WITH_KEY,
ARG_TPM2_DEVICE,
ARG_TPM2_PCRS,
+ ARG_TPM2_PUBLIC_KEY,
+ ARG_TPM2_PUBLIC_KEY_PCRS,
+ ARG_TPM2_SIGNATURE,
ARG_NAME,
ARG_TIMESTAMP,
ARG_NOT_AFTER,
};
static const struct option options[] = {
- { "help", no_argument, NULL, 'h' },
- { "version", no_argument, NULL, ARG_VERSION },
- { "no-pager", no_argument, NULL, ARG_NO_PAGER },
- { "no-legend", no_argument, NULL, ARG_NO_LEGEND },
- { "json", required_argument, NULL, ARG_JSON },
- { "system", no_argument, NULL, ARG_SYSTEM },
- { "transcode", required_argument, NULL, ARG_TRANSCODE },
- { "newline", required_argument, NULL, ARG_NEWLINE },
- { "pretty", no_argument, NULL, 'p' },
- { "with-key", required_argument, NULL, ARG_WITH_KEY },
- { "tpm2-device", required_argument, NULL, ARG_TPM2_DEVICE },
- { "tpm2-pcrs", required_argument, NULL, ARG_TPM2_PCRS },
- { "name", required_argument, NULL, ARG_NAME },
- { "timestamp", required_argument, NULL, ARG_TIMESTAMP },
- { "not-after", required_argument, NULL, ARG_NOT_AFTER },
- { "quiet", no_argument, NULL, 'q' },
+ { "help", no_argument, NULL, 'h' },
+ { "version", no_argument, NULL, ARG_VERSION },
+ { "no-pager", no_argument, NULL, ARG_NO_PAGER },
+ { "no-legend", no_argument, NULL, ARG_NO_LEGEND },
+ { "json", required_argument, NULL, ARG_JSON },
+ { "system", no_argument, NULL, ARG_SYSTEM },
+ { "transcode", required_argument, NULL, ARG_TRANSCODE },
+ { "newline", required_argument, NULL, ARG_NEWLINE },
+ { "pretty", no_argument, NULL, 'p' },
+ { "with-key", required_argument, NULL, ARG_WITH_KEY },
+ { "tpm2-device", required_argument, NULL, ARG_TPM2_DEVICE },
+ { "tpm2-pcrs", required_argument, NULL, ARG_TPM2_PCRS },
+ { "tpm2-public-key", required_argument, NULL, ARG_TPM2_PUBLIC_KEY },
+ { "tpm2-public-key-pcrs", required_argument, NULL, ARG_TPM2_PUBLIC_KEY_PCRS },
+ { "tpm2-signature", required_argument, NULL, ARG_TPM2_SIGNATURE },
+ { "name", required_argument, NULL, ARG_NAME },
+ { "timestamp", required_argument, NULL, ARG_TIMESTAMP },
+ { "not-after", required_argument, NULL, ARG_NOT_AFTER },
+ { "quiet", no_argument, NULL, 'q' },
{}
};
@@ -808,8 +831,12 @@ static int parse_argv(int argc, char *argv[]) {
arg_with_key = CRED_AES256_GCM_BY_HOST;
else if (streq(optarg, "tpm2"))
arg_with_key = CRED_AES256_GCM_BY_TPM2_HMAC;
+ else if (streq(optarg, "tpm2-with-public-key"))
+ arg_with_key = CRED_AES256_GCM_BY_TPM2_HMAC_WITH_PK;
else if (STR_IN_SET(optarg, "host+tpm2", "tpm2+host"))
arg_with_key = CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC;
+ else if (STR_IN_SET(optarg, "host+tpm2-with-public-key", "tpm2-with-public-key+host"))
+ arg_with_key = CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK;
else if (streq(optarg, "tpm2-absent"))
arg_with_key = CRED_AES256_GCM_BY_TPM2_ABSENT;
else
@@ -832,13 +859,34 @@ static int parse_argv(int argc, char *argv[]) {
arg_tpm2_device = streq(optarg, "auto") ? NULL : optarg;
break;
- case ARG_TPM2_PCRS:
+ case ARG_TPM2_PCRS: /* For fixed hash PCR policies only */
r = tpm2_parse_pcr_argument(optarg, &arg_tpm2_pcr_mask);
if (r < 0)
return r;
break;
+ case ARG_TPM2_PUBLIC_KEY:
+ r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_tpm2_public_key);
+ if (r < 0)
+ return r;
+
+ break;
+
+ case ARG_TPM2_PUBLIC_KEY_PCRS: /* For public key PCR policies only */
+ r = tpm2_parse_pcr_argument(optarg, &arg_tpm2_public_key_pcr_mask);
+ if (r < 0)
+ return r;
+
+ break;
+
+ case ARG_TPM2_SIGNATURE:
+ r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_tpm2_signature);
+ if (r < 0)
+ return r;
+
+ break;
+
case ARG_NAME:
if (isempty(optarg)) {
arg_name = NULL;
@@ -881,6 +929,8 @@ static int parse_argv(int argc, char *argv[]) {
if (arg_tpm2_pcr_mask == UINT32_MAX)
arg_tpm2_pcr_mask = TPM2_PCR_MASK_DEFAULT;
+ if (arg_tpm2_public_key_pcr_mask == UINT32_MAX)
+ arg_tpm2_public_key_pcr_mask = UINT32_C(1) << TPM_PCR_INDEX_KERNEL_IMAGE;
return 1;
}
diff --git a/src/cryptenroll/cryptenroll-tpm2.c b/src/cryptenroll/cryptenroll-tpm2.c
index 01f2e1183c..5c902908c4 100644
--- a/src/cryptenroll/cryptenroll-tpm2.c
+++ b/src/cryptenroll/cryptenroll-tpm2.c
@@ -4,6 +4,7 @@
#include "ask-password-api.h"
#include "cryptenroll-tpm2.h"
#include "env-util.h"
+#include "fileio.h"
#include "hexdecoct.h"
#include "json.h"
#include "memory-util.h"
@@ -130,14 +131,17 @@ int enroll_tpm2(struct crypt_device *cd,
const void *volume_key,
size_t volume_key_size,
const char *device,
- uint32_t pcr_mask,
+ uint32_t hash_pcr_mask,
+ const char *pubkey_path,
+ uint32_t pubkey_pcr_mask,
+ const char *signature_path,
bool use_pin) {
- _cleanup_(erase_and_freep) void *secret = NULL, *secret2 = NULL;
- _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
+ _cleanup_(erase_and_freep) void *secret = NULL;
+ _cleanup_(json_variant_unrefp) JsonVariant *v = NULL, *signature_json = NULL;
_cleanup_(erase_and_freep) char *base64_encoded = NULL;
- size_t secret_size, secret2_size, blob_size, hash_size;
- _cleanup_free_ void *blob = NULL, *hash = NULL;
+ size_t secret_size, blob_size, hash_size, pubkey_size = 0;
+ _cleanup_free_ void *blob = NULL, *hash = NULL, *pubkey = NULL;
uint16_t pcr_bank, primary_alg;
const char *node;
_cleanup_(erase_and_freep) char *pin_str = NULL;
@@ -147,7 +151,8 @@ int enroll_tpm2(struct crypt_device *cd,
assert(cd);
assert(volume_key);
assert(volume_key_size > 0);
- assert(TPM2_PCR_MASK_VALID(pcr_mask));
+ assert(TPM2_PCR_MASK_VALID(hash_pcr_mask));
+ assert(TPM2_PCR_MASK_VALID(pubkey_pcr_mask));
assert_se(node = crypt_get_device_name(cd));
@@ -157,7 +162,35 @@ int enroll_tpm2(struct crypt_device *cd,
return r;
}
- r = tpm2_seal(device, pcr_mask, pin_str, &secret, &secret_size, &blob, &blob_size, &hash, &hash_size, &pcr_bank, &primary_alg);
+ r = tpm2_load_pcr_public_key(pubkey_path, &pubkey, &pubkey_size);
+ if (r < 0) {
+ if (pubkey_path || signature_path || r != -ENOENT)
+ return log_error_errno(r, "Failed read TPM PCR public key: %m");
+
+ log_debug_errno(r, "Failed to read TPM2 PCR public key, proceeding without: %m");
+ pubkey_pcr_mask = 0;
+ } else {
+ /* Also try to load the signature JSON object, to verify that our enrollment will work. This is optional however. */
+
+ r = tpm2_load_pcr_signature(signature_path, &signature_json);
+ if (r < 0) {
+ if (signature_path || r != -ENOENT)
+ return log_debug_errno(r, "Failed to read TPM PCR signature: %m");
+
+ log_debug_errno(r, "Failed to read TPM2 PCR signature, proceeding without: %m");
+ }
+ }
+
+ r = tpm2_seal(device,
+ hash_pcr_mask,
+ pubkey, pubkey_size,
+ pubkey_pcr_mask,
+ pin_str,
+ &secret, &secret_size,
+ &blob, &blob_size,
+ &hash, &hash_size,
+ &pcr_bank,
+ &primary_alg);
if (r < 0)
return r;
@@ -172,14 +205,29 @@ int enroll_tpm2(struct crypt_device *cd,
return r; /* return existing keyslot, so that wiping won't kill it */
}
- /* Quick verification that everything is in order, we are not in a hurry after all. */
- log_debug("Unsealing for verification...");
- r = tpm2_unseal(device, pcr_mask, pcr_bank, primary_alg, blob, blob_size, hash, hash_size, pin_str, &secret2, &secret2_size);
- if (r < 0)
- return r;
+ /* Quick verification that everything is in order, we are not in a hurry after all.*/
+ if (!pubkey || signature_json) {
+ _cleanup_(erase_and_freep) void *secret2 = NULL;
+ size_t secret2_size;
- if (memcmp_nn(secret, secret_size, secret2, secret2_size) != 0)
- return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "TPM2 seal/unseal verification failed.");
+ log_debug("Unsealing for verification...");
+ r = tpm2_unseal(device,
+ hash_pcr_mask,
+ pcr_bank,
+ pubkey, pubkey_size,
+ pubkey_pcr_mask,
+ signature_json,
+ pin_str,
+ primary_alg,
+ blob, blob_size,
+ hash, hash_size,
+ &secret2, &secret2_size);
+ if (r < 0)
+ return r;
+
+ if (memcmp_nn(secret, secret_size, secret2, secret2_size) != 0)
+ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "TPM2 seal/unseal verification failed.");
+ }
/* let's base64 encode the key to use, for compat with homed (and it's easier to every type it in by keyboard, if that might end up being necessary. */
r = base64mem(secret, secret_size, &base64_encoded);
@@ -200,7 +248,17 @@ int enroll_tpm2(struct crypt_device *cd,
if (keyslot < 0)
return log_error_errno(keyslot, "Failed to add new TPM2 key to %s: %m", node);
- r = tpm2_make_luks2_json(keyslot, pcr_mask, pcr_bank, primary_alg, blob, blob_size, hash, hash_size, flags, &v);
+ r = tpm2_make_luks2_json(
+ keyslot,
+ hash_pcr_mask,
+ pcr_bank,
+ pubkey, pubkey_size,
+ pubkey_pcr_mask,
+ primary_alg,
+ blob, blob_size,
+ hash, hash_size,
+ flags,
+ &v);
if (r < 0)
return log_error_errno(r, "Failed to prepare TPM2 JSON token object: %m");
diff --git a/src/cryptenroll/cryptenroll-tpm2.h b/src/cryptenroll/cryptenroll-tpm2.h
index 742f49b8d5..b6e0c2808e 100644
--- a/src/cryptenroll/cryptenroll-tpm2.h
+++ b/src/cryptenroll/cryptenroll-tpm2.h
@@ -7,9 +7,9 @@
#include "log.h"
#if HAVE_TPM2
-int enroll_tpm2(struct crypt_device *cd, const void *volume_key, size_t volume_key_size, const char *device, uint32_t pcr_mask, bool use_pin);
+int enroll_tpm2(struct crypt_device *cd, const void *volume_key, size_t volume_key_size, const char *device, uint32_t hash_pcr_mask, const char *pubkey_path, uint32_t pubkey_pcr_mask, const char *signature_path, bool use_pin);
#else
-static inline int enroll_tpm2(struct crypt_device *cd, const void *volume_key, size_t volume_key_size, const char *device, uint32_t pcr_mask, bool use_pin) {
+static inline int enroll_tpm2(struct crypt_device *cd, const void *volume_key, size_t volume_key_size, const char *device, uint32_t hash_pcr_mask, const char *pubkey_path, uint32_t pubkey_pcr_mask, const char *signature_path, bool use_pin) {
return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
"TPM2 key enrollment not supported.");
}
diff --git a/src/cryptenroll/cryptenroll.c b/src/cryptenroll/cryptenroll.c
index 3c2b914a43..6a9170f000 100644
--- a/src/cryptenroll/cryptenroll.c
+++ b/src/cryptenroll/cryptenroll.c
@@ -26,6 +26,7 @@
#include "string-table.h"
#include "strv.h"
#include "terminal-util.h"
+#include "tpm-pcr.h"
#include "tpm2-util.h"
static EnrollType arg_enroll_type = _ENROLL_TYPE_INVALID;
@@ -35,6 +36,9 @@ static char *arg_fido2_device = NULL;
static char *arg_tpm2_device = NULL;
static uint32_t arg_tpm2_pcr_mask = UINT32_MAX;
static bool arg_tpm2_pin = false;
+static char *arg_tpm2_public_key = NULL;
+static uint32_t arg_tpm2_public_key_pcr_mask = UINT32_MAX;
+static char *arg_tpm2_signature = NULL;
static char *arg_node = NULL;
static int *arg_wipe_slots = NULL;
static size_t arg_n_wipe_slots = 0;
@@ -53,6 +57,8 @@ STATIC_DESTRUCTOR_REGISTER(arg_unlock_keyfile, freep);
STATIC_DESTRUCTOR_REGISTER(arg_pkcs11_token_uri, freep);
STATIC_DESTRUCTOR_REGISTER(arg_fido2_device, freep);
STATIC_DESTRUCTOR_REGISTER(arg_tpm2_device, freep);
+STATIC_DESTRUCTOR_REGISTER(arg_tpm2_public_key, freep);
+STATIC_DESTRUCTOR_REGISTER(arg_tpm2_signature, freep);
STATIC_DESTRUCTOR_REGISTER(arg_node, freep);
STATIC_DESTRUCTOR_REGISTER(arg_wipe_slots, freep);
@@ -114,6 +120,13 @@ static int help(void) {
" Enroll a TPM2 device\n"
" --tpm2-pcrs=PCR1+PCR2+PCR3+…\n"
" Specify TPM2 PCRs to seal against\n"
+ " --tpm2-public-key=PATH\n"
+ " Enroll signed TPM2 PCR policy against PEM public key\n"
+ " --tpm2-public-key-pcrs=PCR1+PCR2+PCR3+…\n"
+ " Enroll signed TPM2 PCR policy for specified TPM2 PCRs\n"
+ " --tpm2-signature=PATH\n"
+ " Validate public key enrollment works with JSON signature\n"
+ " file\n"
" --tpm2-with-pin=BOOL\n"
" Whether to require entering a PIN to unlock the volume\n"
" --wipe-slot=SLOT1,SLOT2,…\n"
@@ -138,6 +151,9 @@ static int parse_argv(int argc, char *argv[]) {
ARG_FIDO2_DEVICE,
ARG_TPM2_DEVICE,
ARG_TPM2_PCRS,
+ ARG_TPM2_PUBLIC_KEY,
+ ARG_TPM2_PUBLIC_KEY_PCRS,
+ ARG_TPM2_SIGNATURE,
ARG_TPM2_PIN,
ARG_WIPE_SLOT,
ARG_FIDO2_WITH_PIN,
@@ -147,21 +163,24 @@ static int parse_argv(int argc, char *argv[]) {
};
static const struct option options[] = {
- { "help", no_argument, NULL, 'h' },
- { "version", no_argument, NULL, ARG_VERSION },
- { "password", no_argument, NULL, ARG_PASSWORD },
- { "recovery-key", no_argument, NULL, ARG_RECOVERY_KEY },
- { "unlock-key-file", required_argument, NULL, ARG_UNLOCK_KEYFILE },
- { "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 },
- { "fido2-with-client-pin", required_argument, NULL, ARG_FIDO2_WITH_PIN },
- { "fido2-with-user-presence", required_argument, NULL, ARG_FIDO2_WITH_UP },
- { "fido2-with-user-verification", required_argument, NULL, ARG_FIDO2_WITH_UV },
- { "tpm2-device", required_argument, NULL, ARG_TPM2_DEVICE },
- { "tpm2-pcrs", required_argument, NULL, ARG_TPM2_PCRS },
- { "tpm2-with-pin", required_argument, NULL, ARG_TPM2_PIN },
- { "wipe-slot", required_argument, NULL, ARG_WIPE_SLOT },
+ { "help", no_argument, NULL, 'h' },
+ { "version", no_argument, NULL, ARG_VERSION },
+ { "password", no_argument, NULL, ARG_PASSWORD },
+ { "recovery-key", no_argument, NULL, ARG_RECOVERY_KEY },
+ { "unlock-key-file", required_argument, NULL, ARG_UNLOCK_KEYFILE },
+ { "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 },
+ { "fido2-with-client-pin", required_argument, NULL, ARG_FIDO2_WITH_PIN },
+ { "fido2-with-user-presence", required_argument, NULL, ARG_FIDO2_WITH_UP },
+ { "fido2-with-user-verification", required_argument, NULL, ARG_FIDO2_WITH_UV },
+ { "tpm2-device", required_argument, NULL, ARG_TPM2_DEVICE },
+ { "tpm2-pcrs", required_argument, NULL, ARG_TPM2_PCRS },
+ { "tpm2-public-key", required_argument, NULL, ARG_TPM2_PUBLIC_KEY },
+ { "tpm2-public-key-pcrs", required_argument, NULL, ARG_TPM2_PUBLIC_KEY_PCRS },
+ { "tpm2-signature", required_argument, NULL, ARG_TPM2_SIGNATURE },
+ { "tpm2-with-pin", required_argument, NULL, ARG_TPM2_PIN },
+ { "wipe-slot", required_argument, NULL, ARG_WIPE_SLOT },
{}
};
@@ -329,6 +348,27 @@ static int parse_argv(int argc, char *argv[]) {
break;
+ case ARG_TPM2_PUBLIC_KEY:
+ r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_tpm2_public_key);
+ if (r < 0)
+ return r;
+
+ break;
+
+ case ARG_TPM2_PUBLIC_KEY_PCRS:
+ r = tpm2_parse_pcr_argument(optarg, &arg_tpm2_public_key_pcr_mask);
+ if (r < 0)
+ return r;
+
+ break;
+
+ case ARG_TPM2_SIGNATURE:
+ r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_tpm2_signature);
+ if (r < 0)
+ return r;
+
+ break;
+
case ARG_WIPE_SLOT: {
const char *p = optarg;
@@ -405,6 +445,8 @@ static int parse_argv(int argc, char *argv[]) {
if (arg_tpm2_pcr_mask == UINT32_MAX)
arg_tpm2_pcr_mask = TPM2_PCR_MASK_DEFAULT;
+ if (arg_tpm2_public_key_pcr_mask == UINT32_MAX)
+ arg_tpm2_public_key_pcr_mask = UINT32_C(1) << TPM_PCR_INDEX_KERNEL_IMAGE;
return 1;
}
@@ -615,7 +657,7 @@ static int run(int argc, char *argv[]) {
break;
case ENROLL_TPM2:
- slot = enroll_tpm2(cd, vk, vks, arg_tpm2_device, arg_tpm2_pcr_mask, arg_tpm2_pin);
+ slot = enroll_tpm2(cd, vk, vks, arg_tpm2_device, arg_tpm2_pcr_mask, arg_tpm2_public_key, arg_tpm2_public_key_pcr_mask, arg_tpm2_signature, arg_tpm2_pin);
break;
case _ENROLL_TYPE_INVALID:
diff --git a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c
index bbc8a39c98..7e958a4db5 100644
--- a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c
+++ b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c
@@ -40,27 +40,28 @@ _public_ int cryptsetup_token_open_pin(
int token /* is always >= 0 */,
const char *pin,
size_t pin_size,
- char **password, /* freed by cryptsetup_token_buffer_free */
- size_t *password_len,
+ char **ret_password, /* freed by cryptsetup_token_buffer_free */
+ size_t *ret_password_len,
void *usrptr /* plugin defined parameter passed to crypt_activate_by_token*() API */) {
- int r;
- const char *json;
- size_t blob_size, policy_hash_size, decrypted_key_size;
- uint32_t pcr_mask;
- uint16_t pcr_bank, primary_alg;
+ _cleanup_(erase_and_freep) char *base64_encoded = NULL, *pin_string = NULL;
+ _cleanup_free_ void *blob = NULL, *pubkey = NULL, *policy_hash = NULL;
+ size_t blob_size, policy_hash_size, decrypted_key_size, pubkey_size;
+ _cleanup_(erase_and_freep) void *decrypted_key = NULL;
+ _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
+ uint32_t hash_pcr_mask, pubkey_pcr_mask;
systemd_tpm2_plugin_params params = {
.search_pcr_mask = UINT32_MAX
};
- _cleanup_free_ void *blob = NULL, *policy_hash = NULL;
- _cleanup_free_ char *base64_blob = NULL, *hex_policy_hash = NULL;
- _cleanup_(erase_and_freep) void *decrypted_key = NULL;
- _cleanup_(erase_and_freep) char *base64_encoded = NULL, *pin_string = NULL;
+ uint16_t pcr_bank, primary_alg;
+ TPM2Flags flags = 0;
+ const char *json;
+ int r;
- assert(!pin || pin_size);
- assert(password);
- assert(password_len);
assert(token >= 0);
+ assert(!pin || pin_size > 0);
+ assert(ret_password);
+ assert(ret_password_len);
/* This must not fail at this moment (internal error) */
r = crypt_token_json_get(cd, token, &json);
@@ -74,32 +75,44 @@ _public_ int cryptsetup_token_open_pin(
if (usrptr)
params = *(systemd_tpm2_plugin_params *)usrptr;
- TPM2Flags flags = 0;
- r = parse_luks2_tpm2_data(json, params.search_pcr_mask, &pcr_mask, &pcr_bank, &primary_alg, &base64_blob, &hex_policy_hash, &flags);
+ r = json_parse(json, 0, &v, NULL, NULL);
+ if (r < 0)
+ return crypt_log_debug_errno(cd, r, "Failed to parse token JSON data: %m");
+
+ r = tpm2_parse_luks2_json(
+ v,
+ NULL,
+ &hash_pcr_mask,
+ &pcr_bank,
+ &pubkey,
+ &pubkey_size,
+ &pubkey_pcr_mask,
+ &primary_alg,
+ &blob,
+ &blob_size,
+ &policy_hash,
+ &policy_hash_size,
+ &flags);
if (r < 0)
return log_debug_open_error(cd, r);
- /* should not happen since cryptsetup_token_validate have passed */
- r = unbase64mem(base64_blob, SIZE_MAX, &blob, &blob_size);
- if (r < 0)
- return log_debug_open_error(cd, r);
-
- /* should not happen since cryptsetup_token_validate have passed */
- r = unhexmem(hex_policy_hash, SIZE_MAX, &policy_hash, &policy_hash_size);
- if (r < 0)
- return log_debug_open_error(cd, r);
+ if (params.search_pcr_mask != UINT32_MAX && hash_pcr_mask != params.search_pcr_mask)
+ return crypt_log_debug_errno(cd, ENXIO, "PCR mask doesn't match expectation (%" PRIu32 " vs. %" PRIu32 ")", hash_pcr_mask, params.search_pcr_mask);
r = acquire_luks2_key(
- pcr_mask,
- pcr_bank,
- primary_alg,
params.device,
+ hash_pcr_mask,
+ pcr_bank,
+ pubkey, pubkey_size,
+ pubkey_pcr_mask,
+ params.signature_path,
+ pin_string,
+ primary_alg,
blob,
blob_size,
policy_hash,
policy_hash_size,
flags,
- pin_string,
&decrypted_key,
&decrypted_key_size);
if (r < 0)
@@ -111,8 +124,8 @@ _public_ int cryptsetup_token_open_pin(
return log_debug_open_error(cd, r);
/* free'd automatically by libcryptsetup */
- *password_len = strlen(base64_encoded);
- *password = TAKE_PTR(base64_encoded);
+ *ret_password_len = strlen(base64_encoded);
+ *ret_password = TAKE_PTR(base64_encoded);
return 0;
}
@@ -133,11 +146,11 @@ _public_ int cryptsetup_token_open_pin(
_public_ int cryptsetup_token_open(
struct crypt_device *cd, /* is always LUKS2 context */
int token /* is always >= 0 */,
- char **password, /* freed by cryptsetup_token_buffer_free */
- size_t *password_len,
+ char **ret_password, /* freed by cryptsetup_token_buffer_free */
+ size_t *ret_password_len,
void *usrptr /* plugin defined parameter passed to crypt_activate_by_token*() API */) {
- return cryptsetup_token_open_pin(cd, token, NULL, 0, password, password_len, usrptr);
+ return cryptsetup_token_open_pin(cd, token, NULL, 0, ret_password, ret_password_len, usrptr);
}
/*
@@ -156,45 +169,66 @@ _public_ void cryptsetup_token_dump(
struct crypt_device *cd /* is always LUKS2 context */,
const char *json /* validated 'systemd-tpm2' token if cryptsetup_token_validate is defined */) {
- int r;
- TPM2Flags flags = 0;
- uint32_t pcr_mask;
+ _cleanup_free_ char *hash_pcrs_str = NULL, *pubkey_pcrs_str = NULL, *blob_str = NULL, *policy_hash_str = NULL, *pubkey_str = NULL;
+ _cleanup_free_ void *blob = NULL, *pubkey = NULL, *policy_hash = NULL;
+ _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
+ size_t blob_size, policy_hash_size, pubkey_size;
+ uint32_t hash_pcr_mask, pubkey_pcr_mask;
uint16_t pcr_bank, primary_alg;
- size_t decoded_blob_size;
- _cleanup_free_ char *base64_blob = NULL, *hex_policy_hash = NULL,
- *pcrs_str = NULL, *blob_str = NULL, *policy_hash_str = NULL;
- _cleanup_free_ void *decoded_blob = NULL;
+ TPM2Flags flags = 0;
+ int r;
assert(json);
- r = parse_luks2_tpm2_data(json, UINT32_MAX, &pcr_mask, &pcr_bank, &primary_alg, &base64_blob, &hex_policy_hash, &flags);
+ r = json_parse(json, 0, &v, NULL, NULL);
if (r < 0)
- return (void) crypt_log_debug_errno(cd, r, "Failed to parse " TOKEN_NAME " metadata: %m.");
+ return (void) crypt_log_debug_errno(cd, r, "Failed to parse " TOKEN_NAME " JSON object: %m");
- for (uint32_t i = 0; i < TPM2_PCRS_MAX; i++) {
- if ((pcr_mask & (UINT32_C(1) << i)) &&
- ((r = strextendf_with_separator(&pcrs_str, ", ", "%" PRIu32, i)) < 0))
- return (void) crypt_log_debug_errno(cd, r, "Can not dump " TOKEN_NAME " content: %m");
- }
+ r = tpm2_parse_luks2_json(
+ v,
+ NULL,
+ &hash_pcr_mask,
+ &pcr_bank,
+ &pubkey,
+ &pubkey_size,
+ &pubkey_pcr_mask,
+ &primary_alg,
+ &blob,
+ &blob_size,
+ &policy_hash,
+ &policy_hash_size,
+ &flags);
+ if (r < 0)
+ return (void) crypt_log_debug_errno(cd, r, "Failed to parse " TOKEN_NAME " JSON fields: %m");
- r = unbase64mem(base64_blob, SIZE_MAX, &decoded_blob, &decoded_blob_size);
+ r = pcr_mask_to_string(hash_pcr_mask, &hash_pcrs_str);
+ if (r < 0)
+ return (void) crypt_log_debug_errno(cd, r, "Cannot format PCR hash mask: %m");
+
+ r = pcr_mask_to_string(pubkey_pcr_mask, &pubkey_pcrs_str);
+ if (r < 0)
+ return (void) crypt_log_debug_errno(cd, r, "Cannot format PCR hash mask: %m");
+
+ r = crypt_dump_buffer_to_hex_string(blob, blob_size, &blob_str);
if (r < 0)
return (void) crypt_log_debug_errno(cd, r, "Can not dump " TOKEN_NAME " content: %m");
- r = crypt_dump_buffer_to_hex_string(decoded_blob, decoded_blob_size, &blob_str);
+ r = crypt_dump_buffer_to_hex_string(pubkey, pubkey_size, &pubkey_str);
if (r < 0)
return (void) crypt_log_debug_errno(cd, r, "Can not dump " TOKEN_NAME " content: %m");
- r = crypt_dump_hex_string(hex_policy_hash, &policy_hash_str);
+ r = crypt_dump_buffer_to_hex_string(policy_hash, policy_hash_size, &policy_hash_str);
if (r < 0)
return (void) crypt_log_debug_errno(cd, r, "Can not dump " TOKEN_NAME " content: %m");
- crypt_log(cd, "\ttpm2-pcrs: %s\n", strna(pcrs_str));
- crypt_log(cd, "\ttpm2-bank: %s\n", strna(tpm2_pcr_bank_to_string(pcr_bank)));
- crypt_log(cd, "\ttpm2-primary-alg: %s\n", strna(tpm2_primary_alg_to_string(primary_alg)));
- crypt_log(cd, "\ttpm2-blob: %s\n", blob_str);
+ crypt_log(cd, "\ttpm2-hash-pcrs: %s\n", strna(hash_pcrs_str));
+ crypt_log(cd, "\ttpm2-pcr-bank: %s\n", strna(tpm2_pcr_bank_to_string(pcr_bank)));
+ crypt_log(cd, "\ttpm2-pubkey:" CRYPT_DUMP_LINE_SEP "%s\n", pubkey_str);
+ crypt_log(cd, "\ttpm2-pubkey-pcrs: %s\n", strna(pubkey_pcrs_str));
+ crypt_log(cd, "\ttpm2-primary-alg: %s\n", strna(tpm2_primary_alg_to_string(primary_alg)));
+ crypt_log(cd, "\ttpm2-blob: %s\n", blob_str);
crypt_log(cd, "\ttpm2-policy-hash:" CRYPT_DUMP_LINE_SEP "%s\n", policy_hash_str);
- crypt_log(cd, "\ttpm2-pin: %s\n", true_false(flags & TPM2_FLAGS_USE_PIN));
+ crypt_log(cd, "\ttpm2-pin: %s\n", true_false(flags & TPM2_FLAGS_USE_PIN));
}
/*
diff --git a/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.c b/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.c
index 9f5dd46734..be496d4949 100644
--- a/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.c
+++ b/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.c
@@ -13,19 +13,24 @@
#include "tpm2-util.h"
int acquire_luks2_key(
- uint32_t pcr_mask,
- uint16_t pcr_bank,
- uint16_t primary_alg,
const char *device,
+ uint32_t hash_pcr_mask,
+ uint16_t pcr_bank,
+ const void *pubkey,
+ size_t pubkey_size,
+ uint32_t pubkey_pcr_mask,
+ const char *signature_path,
+ const char *pin,
+ uint16_t primary_alg,
const void *key_data,
size_t key_data_size,
const void *policy_hash,
size_t policy_hash_size,
TPM2Flags flags,
- const char *pin,
void **ret_decrypted_key,
size_t *ret_decrypted_key_size) {
+ _cleanup_(json_variant_unrefp) JsonVariant *signature_json = NULL;
_cleanup_free_ char *auto_device = NULL;
int r;
@@ -45,116 +50,22 @@ int acquire_luks2_key(
if ((flags & TPM2_FLAGS_USE_PIN) && !pin)
return -ENOANO;
+ if (pubkey_pcr_mask != 0) {
+ r = tpm2_load_pcr_signature(signature_path, &signature_json);
+ if (r < 0)
+ return r;
+ }
+
return tpm2_unseal(
device,
- pcr_mask, pcr_bank,
+ hash_pcr_mask,
+ pcr_bank,
+ pubkey, pubkey_size,
+ pubkey_pcr_mask,
+ signature_json,
+ pin,
primary_alg,
key_data, key_data_size,
- policy_hash, policy_hash_size, pin,
+ policy_hash, policy_hash_size,
ret_decrypted_key, ret_decrypted_key_size);
}
-
-/* this function expects valid "systemd-tpm2" in json */
-int parse_luks2_tpm2_data(
- const char *json,
- uint32_t search_pcr_mask,
- uint32_t *ret_pcr_mask,
- uint16_t *ret_pcr_bank,
- uint16_t *ret_primary_alg,
- char **ret_base64_blob,
- char **ret_hex_policy_hash,
- TPM2Flags *ret_flags) {
-
- int r;
- JsonVariant *w;
- uint32_t pcr_mask;
- uint16_t pcr_bank = UINT16_MAX, primary_alg = TPM2_ALG_ECC;
- _cleanup_free_ char *base64_blob = NULL, *hex_policy_hash = NULL;
- _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
- TPM2Flags flags = 0;
-
- assert(json);
- assert(ret_pcr_mask);
- assert(ret_pcr_bank);
- assert(ret_primary_alg);
- assert(ret_base64_blob);
- assert(ret_hex_policy_hash);
-
- r = json_parse(json, 0, &v, NULL, NULL);
- if (r < 0)
- return -EINVAL;
-
- w = json_variant_by_key(v, "tpm2-pcrs");
- if (!w)
- return -EINVAL;
-
- r = tpm2_parse_pcr_json_array(w, &pcr_mask);
- if (r < 0)
- return r;
-
- if (search_pcr_mask != UINT32_MAX &&
- search_pcr_mask != pcr_mask)
- return -ENXIO;
-
- w = json_variant_by_key(v, "tpm2-pcr-bank");
- if (w) {
- /* The PCR bank field is optional */
-
- if (!json_variant_is_string(w))
- return -EINVAL;
-
- r = tpm2_pcr_bank_from_string(json_variant_string(w));
- if (r < 0)
- return r;
-
- pcr_bank = r;
- }
-
- w = json_variant_by_key(v, "tpm2-primary-alg");
- if (w) {
- /* The primary key algorithm is optional */
-
- if (!json_variant_is_string(w))
- return -EINVAL;
-
- r = tpm2_primary_alg_from_string(json_variant_string(w));
- if (r < 0)
- return r;
-
- primary_alg = r;
- }
-
- w = json_variant_by_key(v, "tpm2-blob");
- if (!w || !json_variant_is_string(w))
- return -EINVAL;
-
- base64_blob = strdup(json_variant_string(w));
- if (!base64_blob)
- return -ENOMEM;
-
- w = json_variant_by_key(v, "tpm2-policy-hash");
- if (!w || !json_variant_is_string(w))
- return -EINVAL;
-
- hex_policy_hash = strdup(json_variant_string(w));
- if (!hex_policy_hash)
- return -ENOMEM;
-
- w = json_variant_by_key(v, "tpm2-pin");
- if (w) {
- if (!json_variant_is_boolean(w))
- return -EINVAL;
-
- if (json_variant_boolean(w))
- flags |= TPM2_FLAGS_USE_PIN;
- }
-
- *ret_pcr_mask = pcr_mask;
- *ret_pcr_bank = pcr_bank;
- *ret_primary_alg = primary_alg;
- *ret_base64_blob = TAKE_PTR(base64_blob);
- *ret_hex_policy_hash = TAKE_PTR(hex_policy_hash);
- *ret_flags = flags;
-
- return 0;
-}
diff --git a/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.h b/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.h
index 5e33418025..f3625124e5 100644
--- a/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.h
+++ b/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.h
@@ -7,25 +7,19 @@
struct crypt_device;
int acquire_luks2_key(
+ const char *device,
uint32_t pcr_mask,
uint16_t pcr_bank,
+ const void *pubkey,
+ size_t pubkey_size,
+ uint32_t pubkey_pcr_mask,
+ const char *signature_path,
+ const char *pin,
uint16_t primary_alg,
- const char *device,
const void *key_data,
size_t key_data_size,
const void *policy_hash,
size_t policy_hash_size,
TPM2Flags flags,
- const char *pin,
void **ret_decrypted_key,
size_t *ret_decrypted_key_size);
-
-int parse_luks2_tpm2_data(
- const char *json,
- uint32_t search_pcr_mask,
- uint32_t *ret_pcr_mask,
- uint16_t *ret_pcr_bank,
- uint16_t *ret_primary_alg,
- char **ret_base64_blob,
- char **ret_hex_policy_hash,
- TPM2Flags *ret_flags);
diff --git a/src/cryptsetup/cryptsetup-tpm2.c b/src/cryptsetup/cryptsetup-tpm2.c
index c715c8f232..838c02bfc9 100644
--- a/src/cryptsetup/cryptsetup-tpm2.c
+++ b/src/cryptsetup/cryptsetup-tpm2.c
@@ -55,8 +55,12 @@ static int get_pin(usec_t until, AskPasswordFlags ask_password_flags, bool headl
int acquire_tpm2_key(
const char *volume_name,
const char *device,
- uint32_t pcr_mask,
+ uint32_t hash_pcr_mask,
uint16_t pcr_bank,
+ const void *pubkey,
+ size_t pubkey_size,
+ uint32_t pubkey_pcr_mask,
+ const char *signature_path,
uint16_t primary_alg,
const char *key_file,
size_t key_file_size,
@@ -72,6 +76,7 @@ int acquire_tpm2_key(
void **ret_decrypted_key,
size_t *ret_decrypted_key_size) {
+ _cleanup_(json_variant_unrefp) JsonVariant *signature_json = NULL;
_cleanup_free_ void *loaded_blob = NULL;
_cleanup_free_ char *auto_device = NULL;
size_t blob_size;
@@ -111,17 +116,26 @@ int acquire_tpm2_key(
blob = loaded_blob;
}
+ if (pubkey_pcr_mask != 0) {
+ r = tpm2_load_pcr_signature(signature_path, &signature_json);
+ if (r < 0)
+ return r;
+ }
+
if (!(flags & TPM2_FLAGS_USE_PIN))
return tpm2_unseal(
device,
- pcr_mask,
+ hash_pcr_mask,
pcr_bank,
+ pubkey, pubkey_size,
+ pubkey_pcr_mask,
+ signature_json,
+ /* pin= */ NULL,
primary_alg,
blob,
blob_size,
policy_hash,
policy_hash_size,
- NULL,
ret_decrypted_key,
ret_decrypted_key_size);
@@ -135,16 +149,18 @@ int acquire_tpm2_key(
if (r < 0)
return r;
- r = tpm2_unseal(
- device,
- pcr_mask,
+ r = tpm2_unseal(device,
+ hash_pcr_mask,
pcr_bank,
+ pubkey, pubkey_size,
+ pubkey_pcr_mask,
+ signature_json,
+ pin_str,
primary_alg,
blob,
blob_size,
policy_hash,
policy_hash_size,
- pin_str,
ret_decrypted_key,
ret_decrypted_key_size);
/* We get this error in case there is an authentication policy mismatch. This should
@@ -162,31 +178,32 @@ int find_tpm2_auto_data(
struct crypt_device *cd,
uint32_t search_pcr_mask,
int start_token,
- uint32_t *ret_pcr_mask,
+ uint32_t *ret_hash_pcr_mask,
uint16_t *ret_pcr_bank,
+ void **ret_pubkey,
+ size_t *ret_pubkey_size,
+ uint32_t *ret_pubkey_pcr_mask,
uint16_t *ret_primary_alg,
void **ret_blob,
size_t *ret_blob_size,
void **ret_policy_hash,
size_t *ret_policy_hash_size,
+ TPM2Flags *ret_flags,
int *ret_keyslot,
- int *ret_token,
- TPM2Flags *ret_flags) {
+ int *ret_token) {
- _cleanup_free_ void *blob = NULL, *policy_hash = NULL;
- size_t blob_size = 0, policy_hash_size = 0;
- int r, keyslot = -1, token = -1;
- TPM2Flags flags = 0;
- uint32_t pcr_mask = 0;
- uint16_t pcr_bank = UINT16_MAX; /* default: pick automatically */
- uint16_t primary_alg = TPM2_ALG_ECC; /* ECC was the only supported algorithm in systemd < 250, use that as implied default, for compatibility */
+ int r, token;
assert(cd);
for (token = start_token; token < sym_crypt_token_max(CRYPT_LUKS2); token++) {
+ _cleanup_free_ void *blob = NULL, *policy_hash = NULL, *pubkey = NULL;
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
- JsonVariant *w;
- int ks;
+ size_t blob_size, policy_hash_size, pubkey_size;
+ uint32_t hash_pcr_mask, pubkey_pcr_mask;
+ uint16_t pcr_bank, primary_alg;
+ TPM2Flags flags;
+ int keyslot;
r = cryptsetup_get_token_as_json(cd, token, "systemd-tpm2", &v);
if (IN_SET(r, -ENOENT, -EINVAL, -EMEDIUMTYPE))
@@ -194,119 +211,46 @@ int find_tpm2_auto_data(
if (r < 0)
return log_error_errno(r, "Failed to read JSON token data off disk: %m");
- ks = cryptsetup_get_keyslot_from_token(v);
- if (ks < 0) {
- /* Handle parsing errors of the keyslots field gracefully, since it's not 'owned' by
- * us, but by the LUKS2 spec */
- log_warning_errno(ks, "Failed to extract keyslot index from TPM2 JSON data token %i, skipping: %m", token);
+ r = tpm2_parse_luks2_json(
+ v,
+ &keyslot,
+ &hash_pcr_mask,
+ &pcr_bank,
+ &pubkey, &pubkey_size,
+ &pubkey_pcr_mask,
+ &primary_alg,
+ &blob, &blob_size,
+ &policy_hash, &policy_hash_size,
+ &flags);
+ if (r == -EUCLEAN) /* Gracefully handle issues in JSON fields not owned by us */
continue;
- }
-
- w = json_variant_by_key(v, "tpm2-pcrs");
- if (!w)
- return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
- "TPM2 token data lacks 'tpm2-pcrs' field.");
-
- r = tpm2_parse_pcr_json_array(w, &pcr_mask);
if (r < 0)
- return log_error_errno(r, "Failed to parse TPM2 PCR mask: %m");
+ return log_error_errno(r, "Failed to parse TPM2 JSON data: %m");
- if (search_pcr_mask != UINT32_MAX &&
- search_pcr_mask != pcr_mask) /* PCR mask doesn't match what is configured, ignore this entry */
- continue;
+ if (search_pcr_mask == UINT32_MAX ||
+ search_pcr_mask == hash_pcr_mask) {
- assert(keyslot < 0);
- keyslot = ks;
+ if (start_token <= 0)
+ log_info("Automatically discovered security TPM2 token unlocks volume.");
- assert(pcr_bank == UINT16_MAX);
- assert(primary_alg == TPM2_ALG_ECC);
-
- /* The bank field is optional, since it was added in systemd 250 only. Before the bank was
- * hardcoded to SHA256. */
- w = json_variant_by_key(v, "tpm2-pcr-bank");
- if (w) {
- /* The PCR bank field is optional */
-
- if (!json_variant_is_string(w))
- return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
- "TPM2 PCR bank is not a string.");
-
- r = tpm2_pcr_bank_from_string(json_variant_string(w));
- if (r < 0)
- return log_error_errno(r, "TPM2 PCR bank invalid or not supported: %s", json_variant_string(w));
-
- pcr_bank = r;
+ *ret_hash_pcr_mask = hash_pcr_mask;
+ *ret_pcr_bank = pcr_bank;
+ *ret_pubkey = TAKE_PTR(pubkey);
+ *ret_pubkey_size = pubkey_size;
+ *ret_pubkey_pcr_mask = pubkey_pcr_mask;
+ *ret_primary_alg = primary_alg;
+ *ret_blob = TAKE_PTR(blob);
+ *ret_blob_size = blob_size;
+ *ret_policy_hash = TAKE_PTR(policy_hash);
+ *ret_policy_hash_size = policy_hash_size;
+ *ret_keyslot = keyslot;
+ *ret_token = token;
+ *ret_flags = flags;
+ return 0;
}
- /* The primary key algorithm field is optional, since it was also added in systemd 250
- * only. Before the algorithm was hardcoded to ECC. */
- w = json_variant_by_key(v, "tpm2-primary-alg");
- if (w) {
- /* The primary key algorithm is optional */
-
- if (!json_variant_is_string(w))
- return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
- "TPM2 primary key algorithm is not a string.");
-
- r = tpm2_primary_alg_from_string(json_variant_string(w));
- if (r < 0)
- return log_error_errno(r, "TPM2 primary key algorithm invalid or not supported: %s", json_variant_string(w));
-
- primary_alg = r;
- }
-
- assert(!blob);
- w = json_variant_by_key(v, "tpm2-blob");
- if (!w || !json_variant_is_string(w))
- return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
- "TPM2 token data lacks 'tpm2-blob' field.");
-
- r = unbase64mem(json_variant_string(w), SIZE_MAX, &blob, &blob_size);
- if (r < 0)
- return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
- "Invalid base64 data in 'tpm2-blob' field.");
-
- assert(!policy_hash);
- w = json_variant_by_key(v, "tpm2-policy-hash");
- if (!w || !json_variant_is_string(w))
- return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
- "TPM2 token data lacks 'tpm2-policy-hash' field.");
-
- r = unhexmem(json_variant_string(w), SIZE_MAX, &policy_hash, &policy_hash_size);
- if (r < 0)
- return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
- "Invalid base64 data in 'tpm2-policy-hash' field.");
-
- w = json_variant_by_key(v, "tpm2-pin");
- if (w) {
- if (!json_variant_is_boolean(w))
- return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
- "TPM2 PIN policy is not a boolean.");
-
- if (json_variant_boolean(w))
- flags |= TPM2_FLAGS_USE_PIN;
- }
-
- break;
+ /* PCR mask doesn't match what is configured, ignore this entry, let's see next */
}
- if (!blob)
- return log_error_errno(SYNTHETIC_ERRNO(ENXIO),
- "No valid TPM2 token data found.");
-
- if (start_token <= 0)
- log_info("Automatically discovered security TPM2 token unlocks volume.");
-
- *ret_pcr_mask = pcr_mask;
- *ret_blob = TAKE_PTR(blob);
- *ret_blob_size = blob_size;
- *ret_policy_hash = TAKE_PTR(policy_hash);
- *ret_policy_hash_size = policy_hash_size;
- *ret_keyslot = keyslot;
- *ret_token = token;
- *ret_pcr_bank = pcr_bank;
- *ret_primary_alg = primary_alg;
- *ret_flags = flags;
-
- return 0;
+ return log_error_errno(SYNTHETIC_ERRNO(ENXIO), "No valid TPM2 token data found.");
}
diff --git a/src/cryptsetup/cryptsetup-tpm2.h b/src/cryptsetup/cryptsetup-tpm2.h
index ab16d0a18f..a34eb8443d 100644
--- a/src/cryptsetup/cryptsetup-tpm2.h
+++ b/src/cryptsetup/cryptsetup-tpm2.h
@@ -14,8 +14,12 @@
int acquire_tpm2_key(
const char *volume_name,
const char *device,
- uint32_t pcr_mask,
+ uint32_t hash_pcr_mask,
uint16_t pcr_bank,
+ const void *pubkey,
+ size_t pubkey_size,
+ uint32_t pubkey_pcr_mask,
+ const char *signature_path,
uint16_t primary_alg,
const char *key_file,
size_t key_file_size,
@@ -35,24 +39,31 @@ int find_tpm2_auto_data(
struct crypt_device *cd,
uint32_t search_pcr_mask,
int start_token,
- uint32_t *ret_pcr_mask,
+ uint32_t *ret_hash_pcr_mask,
uint16_t *ret_pcr_bank,
+ void **ret_pubkey,
+ size_t *ret_pubkey_size,
+ uint32_t *ret_pubkey_pcr_mask,
uint16_t *ret_primary_alg,
void **ret_blob,
size_t *ret_blob_size,
void **ret_policy_hash,
size_t *ret_policy_hash_size,
+ TPM2Flags *ret_flags,
int *ret_keyslot,
- int *ret_token,
- TPM2Flags *ret_flags);
+ int *ret_token);
#else
static inline int acquire_tpm2_key(
const char *volume_name,
const char *device,
- uint32_t pcr_mask,
+ uint32_t hash_pcr_mask,
uint16_t pcr_bank,
+ const void *pubkey,
+ size_t pubkey_size,
+ uint32_t pubkey_pcr_mask,
+ const char *signature_path,
uint16_t primary_alg,
const char *key_file,
size_t key_file_size,
@@ -76,16 +87,19 @@ static inline int find_tpm2_auto_data(
struct crypt_device *cd,
uint32_t search_pcr_mask,
int start_token,
- uint32_t *ret_pcr_mask,
+ uint32_t *ret_hash_pcr_mask,
uint16_t *ret_pcr_bank,
+ void **ret_pubkey,
+ size_t *ret_pubkey_size,
+ uint32_t *ret_pubkey_pcr_mask,
uint16_t *ret_primary_alg,
void **ret_blob,
size_t *ret_blob_size,
void **ret_policy_hash,
size_t *ret_policy_hash_size,
+ TPM2Flags *ret_flags,
int *ret_keyslot,
- int *ret_token,
- TPM2Flags *ret_flags) {
+ int *ret_token) {
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
"TPM2 support not available.");
diff --git a/src/cryptsetup/cryptsetup.c b/src/cryptsetup/cryptsetup.c
index 90bce953de..d6e5833b24 100644
--- a/src/cryptsetup/cryptsetup.c
+++ b/src/cryptsetup/cryptsetup.c
@@ -92,6 +92,7 @@ static char *arg_fido2_rp_id = NULL;
static char *arg_tpm2_device = NULL;
static bool arg_tpm2_device_auto = false;
static uint32_t arg_tpm2_pcr_mask = UINT32_MAX;
+static char *arg_tpm2_signature = NULL;
static bool arg_tpm2_pin = false;
static bool arg_headless = false;
static usec_t arg_token_timeout_usec = 30*USEC_PER_SEC;
@@ -105,6 +106,7 @@ STATIC_DESTRUCTOR_REGISTER(arg_fido2_device, freep);
STATIC_DESTRUCTOR_REGISTER(arg_fido2_cid, freep);
STATIC_DESTRUCTOR_REGISTER(arg_fido2_rp_id, freep);
STATIC_DESTRUCTOR_REGISTER(arg_tpm2_device, freep);
+STATIC_DESTRUCTOR_REGISTER(arg_tpm2_signature, freep);
static const char* const passphrase_type_table[_PASSPHRASE_TYPE_MAX] = {
[PASSPHRASE_REGULAR] = "passphrase",
@@ -398,6 +400,16 @@ static int parse_one_option(const char *option) {
if (r < 0)
return r;
+ } else if ((val = startswith(option, "tpm2-signature="))) {
+
+ if (!path_is_absolute(val))
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "TPM2 signature path \"%s\" is not absolute, refusing.", val);
+
+ r = free_and_strdup(&arg_tpm2_signature, val);
+ if (r < 0)
+ return log_oom();
+
} else if ((val = startswith(option, "tpm2-pin="))) {
r = parse_boolean(val);
@@ -1384,7 +1396,8 @@ static int attach_luks2_by_tpm2_via_plugin(
#if HAVE_LIBCRYPTSETUP_PLUGINS
systemd_tpm2_plugin_params params = {
.search_pcr_mask = arg_tpm2_pcr_mask,
- .device = arg_tpm2_device
+ .device = arg_tpm2_device,
+ .signature_path = arg_tpm2_signature,
};
if (!libcryptsetup_plugins_support())
@@ -1441,10 +1454,13 @@ static int attach_luks_or_plain_or_bitlk_by_tpm2(
arg_tpm2_device,
arg_tpm2_pcr_mask == UINT32_MAX ? TPM2_PCR_MASK_DEFAULT : arg_tpm2_pcr_mask,
UINT16_MAX,
- 0,
+ /* pubkey= */ NULL, /* pubkey_size= */ 0,
+ /* pubkey_pcr_mask= */ 0,
+ /* signature_path= */ NULL,
+ /* primary_alg= */ 0,
key_file, arg_keyfile_size, arg_keyfile_offset,
key_data, key_data_size,
- NULL, 0, /* we don't know the policy hash */
+ /* policy_hash= */ NULL, /* policy_hash_size= */ 0, /* we don't know the policy hash */
arg_tpm2_pin,
until,
arg_headless,
@@ -1490,7 +1506,9 @@ static int attach_luks_or_plain_or_bitlk_by_tpm2(
* works. */
for (;;) {
- uint32_t pcr_mask;
+ _cleanup_free_ void *pubkey = NULL;
+ size_t pubkey_size = 0;
+ uint32_t hash_pcr_mask, pubkey_pcr_mask;
uint16_t pcr_bank, primary_alg;
TPM2Flags tpm2_flags;
@@ -1498,14 +1516,16 @@ static int attach_luks_or_plain_or_bitlk_by_tpm2(
cd,
arg_tpm2_pcr_mask, /* if != UINT32_MAX we'll only look for tokens with this PCR mask */
token, /* search for the token with this index, or any later index than this */
- &pcr_mask,
+ &hash_pcr_mask,
&pcr_bank,
+ &pubkey, &pubkey_size,
+ &pubkey_pcr_mask,
&primary_alg,
&blob, &blob_size,
&policy_hash, &policy_hash_size,
+ &tpm2_flags,
&keyslot,
- &token,
- &tpm2_flags);
+ &token);
if (r == -ENXIO)
/* No further TPM2 tokens found in the LUKS2 header. */
return log_full_errno(found_some ? LOG_NOTICE : LOG_DEBUG,
@@ -1523,10 +1543,13 @@ static int attach_luks_or_plain_or_bitlk_by_tpm2(
r = acquire_tpm2_key(
name,
arg_tpm2_device,
- pcr_mask,
+ hash_pcr_mask,
pcr_bank,
+ pubkey, pubkey_size,
+ pubkey_pcr_mask,
+ arg_tpm2_signature,
primary_alg,
- NULL, 0, 0, /* no key file */
+ /* key_file= */ NULL, /* key_file_size= */ 0, /* key_file_offset= */ 0, /* no key file */
blob, blob_size,
policy_hash, policy_hash_size,
tpm2_flags,
diff --git a/src/partition/repart.c b/src/partition/repart.c
index 889de712e2..1206fbfb1d 100644
--- a/src/partition/repart.c
+++ b/src/partition/repart.c
@@ -63,6 +63,7 @@
#include "strv.h"
#include "sync-util.h"
#include "terminal-util.h"
+#include "tpm-pcr.h"
#include "tpm2-util.h"
#include "user-util.h"
#include "utf8.h"
@@ -112,12 +113,15 @@ static void *arg_key = NULL;
static size_t arg_key_size = 0;
static char *arg_tpm2_device = NULL;
static uint32_t arg_tpm2_pcr_mask = UINT32_MAX;
+static char *arg_tpm2_public_key = NULL;
+static uint32_t arg_tpm2_public_key_pcr_mask = UINT32_MAX;
STATIC_DESTRUCTOR_REGISTER(arg_root, freep);
STATIC_DESTRUCTOR_REGISTER(arg_image, freep);
STATIC_DESTRUCTOR_REGISTER(arg_definitions, strv_freep);
STATIC_DESTRUCTOR_REGISTER(arg_key, erase_and_freep);
STATIC_DESTRUCTOR_REGISTER(arg_tpm2_device, freep);
+STATIC_DESTRUCTOR_REGISTER(arg_tpm2_public_key, freep);
typedef struct Partition Partition;
typedef struct FreeArea FreeArea;
@@ -2914,12 +2918,33 @@ static int partition_encrypt(
_cleanup_(erase_and_freep) char *base64_encoded = NULL;
_cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
_cleanup_(erase_and_freep) void *secret = NULL;
+ _cleanup_free_ void *pubkey = NULL;
_cleanup_free_ void *blob = NULL, *hash = NULL;
- size_t secret_size, blob_size, hash_size;
+ size_t secret_size, blob_size, hash_size, pubkey_size = 0;
uint16_t pcr_bank, primary_alg;
int keyslot;
- r = tpm2_seal(arg_tpm2_device, arg_tpm2_pcr_mask, NULL, &secret, &secret_size, &blob, &blob_size, &hash, &hash_size, &pcr_bank, &primary_alg);
+ if (arg_tpm2_public_key_pcr_mask != 0) {
+ r = tpm2_load_pcr_public_key(arg_tpm2_public_key, &pubkey, &pubkey_size);
+ if (r < 0) {
+ if (arg_tpm2_public_key || r != -ENOENT)
+ return log_error_errno(r, "Failed read TPM PCR public key: %m");
+
+ log_debug_errno(r, "Failed to read TPM2 PCR public key, proceeding without: %m");
+ arg_tpm2_public_key_pcr_mask = 0;
+ }
+ }
+
+ r = tpm2_seal(arg_tpm2_device,
+ arg_tpm2_pcr_mask,
+ pubkey, pubkey_size,
+ arg_tpm2_public_key_pcr_mask,
+ /* pin= */ NULL,
+ &secret, &secret_size,
+ &blob, &blob_size,
+ &hash, &hash_size,
+ &pcr_bank,
+ &primary_alg);
if (r < 0)
return log_error_errno(r, "Failed to seal to TPM2: %m");
@@ -2941,7 +2966,17 @@ static int partition_encrypt(
if (keyslot < 0)
return log_error_errno(keyslot, "Failed to add new TPM2 key to %s: %m", node);
- r = tpm2_make_luks2_json(keyslot, arg_tpm2_pcr_mask, pcr_bank, primary_alg, blob, blob_size, hash, hash_size, 0, &v);
+ r = tpm2_make_luks2_json(
+ keyslot,
+ arg_tpm2_pcr_mask,
+ pcr_bank,
+ pubkey, pubkey_size,
+ arg_tpm2_public_key_pcr_mask,
+ primary_alg,
+ blob, blob_size,
+ hash, hash_size,
+ 0,
+ &v);
if (r < 0)
return log_error_errno(r, "Failed to prepare TPM2 JSON token object: %m");
@@ -4442,6 +4477,10 @@ static int help(void) {
" --tpm2-device=PATH Path to TPM2 device node to use\n"
" --tpm2-pcrs=PCR1+PCR2+PCR3+…\n"
" TPM2 PCR indexes to use for TPM2 enrollment\n"
+ " --tpm2-public-key=PATH\n"
+ " Enroll signed TPM2 PCR policy against PEM public key\n"
+ " --tpm2-public-key-pcrs=PCR1+PCR2+PCR3+…\n"
+ " Enroll signed TPM2 PCR policy for specified TPM2 PCRs\n"
" --seed=UUID 128bit seed UUID to derive all UUIDs from\n"
" --size=BYTES Grow loopback file to specified size\n"
" --json=pretty|short|off\n"
@@ -4476,28 +4515,32 @@ static int parse_argv(int argc, char *argv[]) {
ARG_KEY_FILE,
ARG_TPM2_DEVICE,
ARG_TPM2_PCRS,
+ ARG_TPM2_PUBLIC_KEY,
+ ARG_TPM2_PUBLIC_KEY_PCRS,
};
static const struct option options[] = {
- { "help", no_argument, NULL, 'h' },
- { "version", no_argument, NULL, ARG_VERSION },
- { "no-pager", no_argument, NULL, ARG_NO_PAGER },
- { "no-legend", no_argument, NULL, ARG_NO_LEGEND },
- { "dry-run", required_argument, NULL, ARG_DRY_RUN },
- { "empty", required_argument, NULL, ARG_EMPTY },
- { "discard", required_argument, NULL, ARG_DISCARD },
- { "factory-reset", required_argument, NULL, ARG_FACTORY_RESET },
- { "can-factory-reset", no_argument, NULL, ARG_CAN_FACTORY_RESET },
- { "root", required_argument, NULL, ARG_ROOT },
- { "image", required_argument, NULL, ARG_IMAGE },
- { "seed", required_argument, NULL, ARG_SEED },
- { "pretty", required_argument, NULL, ARG_PRETTY },
- { "definitions", required_argument, NULL, ARG_DEFINITIONS },
- { "size", required_argument, NULL, ARG_SIZE },
- { "json", required_argument, NULL, ARG_JSON },
- { "key-file", required_argument, NULL, ARG_KEY_FILE },
- { "tpm2-device", required_argument, NULL, ARG_TPM2_DEVICE },
- { "tpm2-pcrs", required_argument, NULL, ARG_TPM2_PCRS },
+ { "help", no_argument, NULL, 'h' },
+ { "version", no_argument, NULL, ARG_VERSION },
+ { "no-pager", no_argument, NULL, ARG_NO_PAGER },
+ { "no-legend", no_argument, NULL, ARG_NO_LEGEND },
+ { "dry-run", required_argument, NULL, ARG_DRY_RUN },
+ { "empty", required_argument, NULL, ARG_EMPTY },
+ { "discard", required_argument, NULL, ARG_DISCARD },
+ { "factory-reset", required_argument, NULL, ARG_FACTORY_RESET },
+ { "can-factory-reset", no_argument, NULL, ARG_CAN_FACTORY_RESET },
+ { "root", required_argument, NULL, ARG_ROOT },
+ { "image", required_argument, NULL, ARG_IMAGE },
+ { "seed", required_argument, NULL, ARG_SEED },
+ { "pretty", required_argument, NULL, ARG_PRETTY },
+ { "definitions", required_argument, NULL, ARG_DEFINITIONS },
+ { "size", required_argument, NULL, ARG_SIZE },
+ { "json", required_argument, NULL, ARG_JSON },
+ { "key-file", required_argument, NULL, ARG_KEY_FILE },
+ { "tpm2-device", required_argument, NULL, ARG_TPM2_DEVICE },
+ { "tpm2-pcrs", required_argument, NULL, ARG_TPM2_PCRS },
+ { "tpm2-public-key", required_argument, NULL, ARG_TPM2_PUBLIC_KEY },
+ { "tpm2-public-key-pcrs", required_argument, NULL, ARG_TPM2_PUBLIC_KEY_PCRS },
{}
};
@@ -4690,6 +4733,20 @@ static int parse_argv(int argc, char *argv[]) {
break;
+ case ARG_TPM2_PUBLIC_KEY:
+ r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_tpm2_public_key);
+ if (r < 0)
+ return r;
+
+ break;
+
+ case ARG_TPM2_PUBLIC_KEY_PCRS:
+ r = tpm2_parse_pcr_argument(optarg, &arg_tpm2_public_key_pcr_mask);
+ if (r < 0)
+ return r;
+
+ break;
+
case '?':
return -EINVAL;
@@ -4743,6 +4800,8 @@ static int parse_argv(int argc, char *argv[]) {
if (arg_tpm2_pcr_mask == UINT32_MAX)
arg_tpm2_pcr_mask = TPM2_PCR_MASK_DEFAULT;
+ if (arg_tpm2_public_key_pcr_mask == UINT32_MAX)
+ arg_tpm2_public_key_pcr_mask = UINT32_C(1) << TPM_PCR_INDEX_KERNEL_IMAGE;
if (arg_pretty < 0 && isatty(STDOUT_FILENO))
arg_pretty = true;
diff --git a/src/shared/creds-util.c b/src/shared/creds-util.c
index 03f5fb8c3f..02d86207c2 100644
--- a/src/shared/creds-util.c
+++ b/src/shared/creds-util.c
@@ -11,6 +11,7 @@
#include "blockdev-util.h"
#include "chattr-util.h"
#include "creds-util.h"
+#include "def.h"
#include "efi-api.h"
#include "env-util.h"
#include "fd-util.h"
@@ -27,6 +28,8 @@
#include "tpm2-util.h"
#include "virt.h"
+#define PUBLIC_KEY_MAX (UINT32_C(1024) * UINT32_C(1024))
+
bool credential_name_valid(const char *s) {
/* We want that credential names are both valid in filenames (since that's our primary way to pass
* them around) and as fdnames (which is how we might want to pass them around eventually) */
@@ -461,6 +464,13 @@ struct _packed_ tpm2_credential_header {
/* Followed by NUL bytes until next 8 byte boundary */
};
+struct _packed_ tpm2_public_key_credential_header {
+ le64_t pcr_mask; /* PCRs used for the public key PCR policy (usually just PCR 11, i.e. the unified kernel) */
+ le32_t size; /* Size of DER public key */
+ uint8_t data[]; /* DER public key */
+ /* Followed by NUL bytes until next 8 byte boundary */
+};
+
struct _packed_ metadata_credential_header {
le64_t timestamp;
le64_t not_after;
@@ -518,7 +528,9 @@ int encrypt_credential_and_warn(
usec_t timestamp,
usec_t not_after,
const char *tpm2_device,
- uint32_t tpm2_pcr_mask,
+ uint32_t tpm2_hash_pcr_mask,
+ const char *tpm2_pubkey_path,
+ uint32_t tpm2_pubkey_pcr_mask,
const void *input,
size_t input_size,
void **ret,
@@ -532,6 +544,8 @@ int encrypt_credential_and_warn(
uint16_t tpm2_pcr_bank = 0, tpm2_primary_alg = 0;
struct encrypted_credential_header *h;
int ksz, bsz, ivsz, tsz, added, r;
+ _cleanup_free_ void *pubkey = NULL;
+ size_t pubkey_size = 0;
uint8_t md[SHA256_DIGEST_LENGTH];
const EVP_CIPHER *cc;
sd_id128_t id;
@@ -545,7 +559,9 @@ int encrypt_credential_and_warn(
_CRED_AUTO_INITRD,
CRED_AES256_GCM_BY_HOST,
CRED_AES256_GCM_BY_TPM2_HMAC,
+ CRED_AES256_GCM_BY_TPM2_HMAC_WITH_PK,
CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC,
+ CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK,
CRED_AES256_GCM_BY_TPM2_ABSENT))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid key type: " SD_ID128_FORMAT_STR, SD_ID128_FORMAT_VAL(with_key));
@@ -569,7 +585,8 @@ int encrypt_credential_and_warn(
if (sd_id128_in_set(with_key,
_CRED_AUTO,
CRED_AES256_GCM_BY_HOST,
- CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC)) {
+ CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC,
+ CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK)) {
r = get_credential_host_secret(
CREDENTIAL_SECRET_GENERATE|
@@ -604,18 +621,41 @@ int encrypt_credential_and_warn(
if (!try_tpm2)
log_debug("Firmware lacks TPM2 support, not attempting to use TPM2.");
} else
- try_tpm2 = sd_id128_in_set(with_key, CRED_AES256_GCM_BY_TPM2_HMAC, CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC);
+ try_tpm2 = sd_id128_in_set(with_key,
+ CRED_AES256_GCM_BY_TPM2_HMAC,
+ CRED_AES256_GCM_BY_TPM2_HMAC_WITH_PK,
+ CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC,
+ CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK);
if (try_tpm2) {
+ if (sd_id128_in_set(with_key,
+ _CRED_AUTO,
+ _CRED_AUTO_INITRD,
+ CRED_AES256_GCM_BY_TPM2_HMAC_WITH_PK,
+ CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK)) {
+
+ /* Load public key for PCR policies, if one is specified, or explicitly requested */
+
+ r = tpm2_load_pcr_public_key(tpm2_pubkey_path, &pubkey, &pubkey_size);
+ if (r < 0) {
+ if (tpm2_pubkey_path || r != -ENOENT || !sd_id128_in_set(with_key, _CRED_AUTO, _CRED_AUTO_INITRD))
+ return log_error_errno(r, "Failed read TPM PCR public key: %m");
+
+ log_debug_errno(r, "Failed to read TPM2 PCR public key, proceeding without: %m");
+ }
+ }
+
+ if (!pubkey)
+ tpm2_pubkey_pcr_mask = 0;
+
r = tpm2_seal(tpm2_device,
- tpm2_pcr_mask,
- NULL,
- &tpm2_key,
- &tpm2_key_size,
- &tpm2_blob,
- &tpm2_blob_size,
- &tpm2_policy_hash,
- &tpm2_policy_hash_size,
+ tpm2_hash_pcr_mask,
+ pubkey, pubkey_size,
+ tpm2_pubkey_pcr_mask,
+ /* pin= */ NULL,
+ &tpm2_key, &tpm2_key_size,
+ &tpm2_blob, &tpm2_blob_size,
+ &tpm2_policy_hash, &tpm2_policy_hash_size,
&tpm2_pcr_bank,
&tpm2_primary_alg);
if (r < 0) {
@@ -636,9 +676,9 @@ int encrypt_credential_and_warn(
/* Let's settle the key type in auto mode now. */
if (host_key && tpm2_key)
- id = CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC;
+ id = pubkey ? CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK : CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC;
else if (tpm2_key)
- id = CRED_AES256_GCM_BY_TPM2_HMAC;
+ id = pubkey ? CRED_AES256_GCM_BY_TPM2_HMAC_WITH_PK : CRED_AES256_GCM_BY_TPM2_HMAC;
else if (host_key)
id = CRED_AES256_GCM_BY_HOST;
else if (sd_id128_equal(with_key, _CRED_AUTO_INITRD))
@@ -694,6 +734,7 @@ int encrypt_credential_and_warn(
output_size =
ALIGN8(offsetof(struct encrypted_credential_header, iv) + ivsz) +
ALIGN8(tpm2_key ? offsetof(struct tpm2_credential_header, policy_hash_and_blob) + tpm2_blob_size + tpm2_policy_hash_size : 0) +
+ ALIGN8(pubkey ? offsetof(struct tpm2_public_key_credential_header, data) + pubkey_size : 0) +
ALIGN8(offsetof(struct metadata_credential_header, name) + strlen_ptr(name)) +
input_size + 2U * (size_t) bsz +
tsz;
@@ -716,7 +757,7 @@ int encrypt_credential_and_warn(
struct tpm2_credential_header *t;
t = (struct tpm2_credential_header*) ((uint8_t*) output + p);
- t->pcr_mask = htole64(tpm2_pcr_mask);
+ t->pcr_mask = htole64(tpm2_hash_pcr_mask);
t->pcr_bank = htole16(tpm2_pcr_bank);
t->primary_alg = htole16(tpm2_primary_alg);
t->blob_size = htole32(tpm2_blob_size);
@@ -727,6 +768,17 @@ int encrypt_credential_and_warn(
p += ALIGN8(offsetof(struct tpm2_credential_header, policy_hash_and_blob) + tpm2_blob_size + tpm2_policy_hash_size);
}
+ if (pubkey) {
+ struct tpm2_public_key_credential_header *z;
+
+ z = (struct tpm2_public_key_credential_header*) ((uint8_t*) output + p);
+ z->pcr_mask = htole64(tpm2_pubkey_pcr_mask);
+ z->size = htole32(pubkey_size);
+ memcpy(z->data, pubkey, pubkey_size);
+
+ p += ALIGN8(offsetof(struct tpm2_public_key_credential_header, data) + pubkey_size);
+ }
+
/* Pass the encrypted + TPM2 header as AAD */
if (EVP_EncryptUpdate(context, NULL, &added, output, p) != 1)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to write AAD data: %s",
@@ -798,18 +850,20 @@ int decrypt_credential_and_warn(
const char *validate_name,
usec_t validate_timestamp,
const char *tpm2_device,
+ const char *tpm2_signature_path,
const void *input,
size_t input_size,
void **ret,
size_t *ret_size) {
_cleanup_(erase_and_freep) void *host_key = NULL, *tpm2_key = NULL, *plaintext = NULL;
+ _cleanup_(json_variant_unrefp) JsonVariant *signature_json = NULL;
_cleanup_(EVP_CIPHER_CTX_freep) EVP_CIPHER_CTX *context = NULL;
size_t host_key_size = 0, tpm2_key_size = 0, plaintext_size, p, hs;
struct encrypted_credential_header *h;
struct metadata_credential_header *m;
uint8_t md[SHA256_DIGEST_LENGTH];
- bool with_tpm2, with_host_key, is_tpm2_absent;
+ bool with_tpm2, with_host_key, is_tpm2_absent, with_tpm2_pk;
const EVP_CIPHER *cc;
int r, added;
@@ -823,13 +877,20 @@ int decrypt_credential_and_warn(
if (input_size < sizeof(h->id))
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Encrypted file too short.");
- with_host_key = sd_id128_in_set(h->id, CRED_AES256_GCM_BY_HOST, CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC);
- with_tpm2 = sd_id128_in_set(h->id, CRED_AES256_GCM_BY_TPM2_HMAC, CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC);
+ with_host_key = sd_id128_in_set(h->id, CRED_AES256_GCM_BY_HOST, CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC, CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK);
+ with_tpm2_pk = sd_id128_in_set(h->id, CRED_AES256_GCM_BY_TPM2_HMAC_WITH_PK, CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK);
+ with_tpm2 = sd_id128_in_set(h->id, CRED_AES256_GCM_BY_TPM2_HMAC, CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC) || with_tpm2_pk;
is_tpm2_absent = sd_id128_equal(h->id, CRED_AES256_GCM_BY_TPM2_ABSENT);
if (!with_host_key && !with_tpm2 && !is_tpm2_absent)
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Unknown encryption format, or corrupted data: %m");
+ if (with_tpm2_pk) {
+ r = tpm2_load_pcr_signature(tpm2_signature_path, &signature_json);
+ if (r < 0)
+ return r;
+ }
+
if (is_tpm2_absent) {
/* So this is a credential encrypted with a zero length key. We support this to cover for the
* case where neither a host key not a TPM2 are available (specifically: initrd environments
@@ -868,7 +929,8 @@ int decrypt_credential_and_warn(
* lower limit only) */
if (input_size <
ALIGN8(offsetof(struct encrypted_credential_header, iv) + le32toh(h->iv_size)) +
- ALIGN8((with_tpm2 ? offsetof(struct tpm2_credential_header, policy_hash_and_blob) : 0)) +
+ ALIGN8(with_tpm2 ? offsetof(struct tpm2_credential_header, policy_hash_and_blob) : 0) +
+ ALIGN8(with_tpm2_pk ? offsetof(struct tpm2_public_key_credential_header, data) : 0) +
ALIGN8(offsetof(struct metadata_credential_header, name)) +
le32toh(h->tag_size))
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Encrypted file too short.");
@@ -878,6 +940,7 @@ int decrypt_credential_and_warn(
if (with_tpm2) {
#if HAVE_TPM2
struct tpm2_credential_header* t = (struct tpm2_credential_header*) ((uint8_t*) input + p);
+ struct tpm2_public_key_credential_header *z = NULL;
if (!TPM2_PCR_MASK_VALID(t->pcr_mask))
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "TPM2 PCR mask out of range.");
@@ -895,27 +958,52 @@ int decrypt_credential_and_warn(
if (input_size <
ALIGN8(offsetof(struct encrypted_credential_header, iv) + le32toh(h->iv_size)) +
ALIGN8(offsetof(struct tpm2_credential_header, policy_hash_and_blob) + le32toh(t->blob_size) + le32toh(t->policy_hash_size)) +
+ ALIGN8(with_tpm2_pk ? offsetof(struct tpm2_public_key_credential_header, data) : 0) +
ALIGN8(offsetof(struct metadata_credential_header, name)) +
le32toh(h->tag_size))
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Encrypted file too short.");
+ p += ALIGN8(offsetof(struct tpm2_credential_header, policy_hash_and_blob) +
+ le32toh(t->blob_size) +
+ le32toh(t->policy_hash_size));
+
+ if (with_tpm2_pk) {
+ z = (struct tpm2_public_key_credential_header*) ((uint8_t*) input + p);
+
+ if (!TPM2_PCR_MASK_VALID(le64toh(z->pcr_mask)) || le64toh(z->pcr_mask) == 0)
+ return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "TPM2 PCR mask out of range.");
+ if (le32toh(z->size) > PUBLIC_KEY_MAX)
+ return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Unexpected public key size.");
+
+ if (input_size <
+ ALIGN8(offsetof(struct encrypted_credential_header, iv) + le32toh(h->iv_size)) +
+ ALIGN8(offsetof(struct tpm2_credential_header, policy_hash_and_blob) + le32toh(t->blob_size) + le32toh(t->policy_hash_size)) +
+ ALIGN8(offsetof(struct tpm2_public_key_credential_header, data) + le32toh(z->size)) +
+ ALIGN8(offsetof(struct metadata_credential_header, name)) +
+ le32toh(h->tag_size))
+ return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Encrypted file too short.");
+
+ p += ALIGN8(offsetof(struct tpm2_public_key_credential_header, data) +
+ le32toh(z->size));
+ }
+
r = tpm2_unseal(tpm2_device,
le64toh(t->pcr_mask),
le16toh(t->pcr_bank),
+ z ? z->data : NULL, z ? le32toh(z->size) : 0,
+ le64toh(z->pcr_mask),
+ signature_json,
+ /* pin= */ NULL,
le16toh(t->primary_alg),
t->policy_hash_and_blob,
le32toh(t->blob_size),
t->policy_hash_and_blob + le32toh(t->blob_size),
le32toh(t->policy_hash_size),
- NULL,
&tpm2_key,
&tpm2_key_size);
if (r < 0)
return r;
- p += ALIGN8(offsetof(struct tpm2_credential_header, policy_hash_and_blob) +
- le32toh(t->blob_size) +
- le32toh(t->policy_hash_size));
#else
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Credential requires TPM2 support, but TPM2 support not available.");
#endif
@@ -1071,11 +1159,11 @@ int get_credential_host_secret(CredentialSecretFlags flags, void **ret, size_t *
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Support for encrypted credentials not available.");
}
-int encrypt_credential_and_warn(sd_id128_t with_key, const char *name, usec_t timestamp, usec_t not_after, const char *tpm2_device, uint32_t tpm2_pcr_mask, const void *input, size_t input_size, void **ret, size_t *ret_size) {
+int encrypt_credential_and_warn(sd_id128_t with_key, const char *name, usec_t timestamp, usec_t not_after, const char *tpm2_device, uint32_t tpm2_hash_pcr_mask, const char *tpm2_pubkey_path, uint32_t tpm2_pubkey_pcr_mask, const void *input, size_t input_size, void **ret, size_t *ret_size) {
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Support for encrypted credentials not available.");
}
-int decrypt_credential_and_warn(const char *validate_name, usec_t validate_timestamp, const char *tpm2_device, const void *input, size_t input_size, void **ret, size_t *ret_size) {
+int decrypt_credential_and_warn(const char *validate_name, usec_t validate_timestamp, const char *tpm2_device, const char *tpm2_signature_path, const void *input, size_t input_size, void **ret, size_t *ret_size) {
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Support for encrypted credentials not available.");
}
diff --git a/src/shared/creds-util.h b/src/shared/creds-util.h
index 62e5c888ee..cf3d6c7dc6 100644
--- a/src/shared/creds-util.h
+++ b/src/shared/creds-util.h
@@ -52,7 +52,10 @@ int get_credential_user_password(const char *username, char **ret_password, bool
* for us to handle). */
#define CRED_AES256_GCM_BY_HOST SD_ID128_MAKE(5a,1c,6a,86,df,9d,40,96,b1,d5,a6,5e,08,62,f1,9a)
#define CRED_AES256_GCM_BY_TPM2_HMAC SD_ID128_MAKE(0c,7c,c0,7b,11,76,45,91,9c,4b,0b,ea,08,bc,20,fe)
+#define CRED_AES256_GCM_BY_TPM2_HMAC_WITH_PK SD_ID128_MAKE(fa,f7,eb,93,41,e3,41,2c,a1,a4,36,f9,5a,29,36,2f)
#define CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC SD_ID128_MAKE(93,a8,94,09,48,74,44,90,90,ca,f2,fc,93,ca,b5,53)
+#define CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK \
+ SD_ID128_MAKE(af,49,50,a8,49,13,4e,b1,a7,38,46,30,4f,f3,0c,05)
#define CRED_AES256_GCM_BY_TPM2_ABSENT SD_ID128_MAKE(05,84,69,da,f6,f5,43,24,80,05,49,da,0f,8e,a2,fb)
/* Two special IDs to pick a general automatic mode (i.e. tpm2+host if TPM2 exists, only host otherwise) or
@@ -63,5 +66,5 @@ int get_credential_user_password(const char *username, char **ret_password, bool
#define _CRED_AUTO SD_ID128_MAKE(a2,19,cb,07,85,b2,4c,04,b1,6d,18,ca,b9,d2,ee,01)
#define _CRED_AUTO_INITRD SD_ID128_MAKE(02,dc,8e,de,3a,02,43,ab,a9,ec,54,9c,05,e6,a0,71)
-int encrypt_credential_and_warn(sd_id128_t with_key, const char *name, usec_t timestamp, usec_t not_after, const char *tpm2_device, uint32_t tpm2_pcr_mask, const void *input, size_t input_size, void **ret, size_t *ret_size);
-int decrypt_credential_and_warn(const char *validate_name, usec_t validate_timestamp, const char *tpm2_device, const void *input, size_t input_size, void **ret, size_t *ret_size);
+int encrypt_credential_and_warn(sd_id128_t with_key, const char *name, usec_t timestamp, usec_t not_after, const char *tpm2_device, uint32_t tpm2_hash_pcr_mask, const char *tpm2_pubkey_path, uint32_t tpm2_pubkey_pcr_mask, const void *input, size_t input_size, void **ret, size_t *ret_size);
+int decrypt_credential_and_warn(const char *validate_name, usec_t validate_timestamp, const char *tpm2_device, const char *tpm2_signature_path, const void *input, size_t input_size, void **ret, size_t *ret_size);
diff --git a/src/shared/cryptsetup-util.c b/src/shared/cryptsetup-util.c
index b93f702aff..da6dcb2f09 100644
--- a/src/shared/cryptsetup-util.c
+++ b/src/shared/cryptsetup-util.c
@@ -1,12 +1,12 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
-#if HAVE_LIBCRYPTSETUP
#include "alloc-util.h"
#include "cryptsetup-util.h"
#include "dlfcn-util.h"
#include "log.h"
#include "parse-util.h"
+#if HAVE_LIBCRYPTSETUP
static void *cryptsetup_dl = NULL;
int (*sym_crypt_activate_by_passphrase)(struct crypt_device *cd, const char *name, int keyslot, const char *passphrase, size_t passphrase_size, uint32_t flags);
@@ -224,6 +224,28 @@ int cryptsetup_get_token_as_json(
return 0;
}
+int cryptsetup_add_token_json(struct crypt_device *cd, JsonVariant *v) {
+ _cleanup_free_ char *text = NULL;
+ int r;
+
+ r = dlopen_cryptsetup();
+ if (r < 0)
+ return r;
+
+ r = json_variant_format(v, 0, &text);
+ if (r < 0)
+ return log_debug_errno(r, "Failed to format token data for LUKS: %m");
+
+ log_debug("Adding token text <%s>", text);
+
+ r = sym_crypt_token_json_set(cd, CRYPT_ANY_TOKEN, text);
+ if (r < 0)
+ return log_debug_errno(r, "Failed to write token data to LUKS: %m");
+
+ return 0;
+}
+#endif
+
int cryptsetup_get_keyslot_from_token(JsonVariant *v) {
int keyslot, r;
JsonVariant *w;
@@ -252,25 +274,3 @@ int cryptsetup_get_keyslot_from_token(JsonVariant *v) {
return keyslot;
}
-
-int cryptsetup_add_token_json(struct crypt_device *cd, JsonVariant *v) {
- _cleanup_free_ char *text = NULL;
- int r;
-
- r = dlopen_cryptsetup();
- if (r < 0)
- return r;
-
- r = json_variant_format(v, 0, &text);
- if (r < 0)
- return log_debug_errno(r, "Failed to format token data for LUKS: %m");
-
- log_debug("Adding token text <%s>", text);
-
- r = sym_crypt_token_json_set(cd, CRYPT_ANY_TOKEN, text);
- if (r < 0)
- return log_debug_errno(r, "Failed to write token data to LUKS: %m");
-
- return 0;
-}
-#endif
diff --git a/src/shared/cryptsetup-util.h b/src/shared/cryptsetup-util.h
index 2a17820d63..fc80edb411 100644
--- a/src/shared/cryptsetup-util.h
+++ b/src/shared/cryptsetup-util.h
@@ -74,7 +74,6 @@ void cryptsetup_enable_logging(struct crypt_device *cd);
int cryptsetup_set_minimal_pbkdf(struct crypt_device *cd);
int cryptsetup_get_token_as_json(struct crypt_device *cd, int idx, const char *verify_type, JsonVariant **ret);
-int cryptsetup_get_keyslot_from_token(JsonVariant *v);
int cryptsetup_add_token_json(struct crypt_device *cd, JsonVariant *v);
#else
@@ -87,6 +86,8 @@ static inline void sym_crypt_freep(struct crypt_device** cd) {}
#endif
+int cryptsetup_get_keyslot_from_token(JsonVariant *v);
+
static inline const char *mangle_none(const char *s) {
/* A helper that turns cryptsetup/integritysetup/veritysetup "options" strings into NULL if they are effectively empty */
return isempty(s) || STR_IN_SET(s, "-", "none") ? NULL : s;
diff --git a/src/shared/openssl-util.c b/src/shared/openssl-util.c
index fdfe465594..2bd2d5e43b 100644
--- a/src/shared/openssl-util.c
+++ b/src/shared/openssl-util.c
@@ -109,6 +109,64 @@ int rsa_pkey_to_suitable_key_size(
return 0;
}
+int pubkey_fingerprint(EVP_PKEY *pk, const EVP_MD *md, void **ret, size_t *ret_size) {
+ _cleanup_(EVP_MD_CTX_freep) EVP_MD_CTX* m = NULL;
+ _cleanup_free_ void *d = NULL, *h = NULL;
+ int sz, lsz, msz;
+ unsigned umsz;
+ unsigned char *dd;
+
+ /* Calculates a message digest of the DER encoded public key */
+
+ assert(pk);
+ assert(md);
+ assert(ret);
+ assert(ret_size);
+
+ sz = i2d_PublicKey(pk, NULL);
+ if (sz < 0)
+ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Unable to convert public key to DER format: %s",
+ ERR_error_string(ERR_get_error(), NULL));
+
+ dd = d = malloc(sz);
+ if (!d)
+ return log_oom_debug();
+
+ lsz = i2d_PublicKey(pk, &dd);
+ if (lsz < 0)
+ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Unable to convert public key to DER format: %s",
+ ERR_error_string(ERR_get_error(), NULL));
+
+ m = EVP_MD_CTX_new();
+ if (!m)
+ return log_oom_debug();
+
+ if (EVP_DigestInit_ex(m, md, NULL) != 1)
+ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to initialize %s context.", EVP_MD_name(md));
+
+ if (EVP_DigestUpdate(m, d, lsz) != 1)
+ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to run %s context.", EVP_MD_name(md));
+
+ msz = EVP_MD_size(md);
+ assert_se(msz > 0);
+ assert_se(msz <= INT_MAX);
+
+ h = malloc(msz);
+ if (!h)
+ return log_oom_debug();
+
+ umsz = msz;
+ if (EVP_DigestFinal_ex(m, h, &umsz) != 1)
+ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to finalize hash context.");
+
+ assert_se(umsz == (unsigned) msz);
+
+ *ret = TAKE_PTR(h);
+ *ret_size = msz;
+
+ return 0;
+}
+
# if PREFER_OPENSSL
int string_hashsum(
const char *s,
diff --git a/src/shared/openssl-util.h b/src/shared/openssl-util.h
index 0f82bc1e00..3dda19582b 100644
--- a/src/shared/openssl-util.h
+++ b/src/shared/openssl-util.h
@@ -8,9 +8,17 @@
# include
# include
# include
+# include
# include
# include
# include
+# ifndef OPENSSL_VERSION_MAJOR
+/* OPENSSL_VERSION_MAJOR macro was added in OpenSSL 3. Thus, if it doesn't exist, we must be before OpenSSL 3. */
+# define OPENSSL_VERSION_MAJOR 1
+# endif
+# if OPENSSL_VERSION_MAJOR >= 3
+# include
+# endif
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(X509*, X509_free, NULL);
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(X509_NAME*, X509_NAME_free, NULL);
@@ -39,6 +47,9 @@ int openssl_hash(const EVP_MD *alg, const void *msg, size_t msg_len, uint8_t *re
int rsa_encrypt_bytes(EVP_PKEY *pkey, const void *decrypted_key, size_t decrypted_key_size, void **ret_encrypt_key, size_t *ret_encrypt_key_size);
int rsa_pkey_to_suitable_key_size(EVP_PKEY *pkey, size_t *ret_suitable_key_size);
+
+int pubkey_fingerprint(EVP_PKEY *pk, const EVP_MD *md, void **ret, size_t *ret_size);
+
#endif
#if PREFER_OPENSSL
diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c
index fdb305ca48..7c9eb77f95 100644
--- a/src/shared/tpm2-util.c
+++ b/src/shared/tpm2-util.c
@@ -1,25 +1,28 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
-#include "efi-api.h"
-#include "extract-word.h"
-#include "parse-util.h"
-#include "stat-util.h"
-#include "tpm2-util.h"
-#include "virt.h"
-
-#if HAVE_TPM2
#include "alloc-util.h"
+#include "cryptsetup-util.h"
+#include "def.h"
#include "dirent-util.h"
#include "dlfcn-util.h"
+#include "efi-api.h"
+#include "extract-word.h"
#include "fd-util.h"
+#include "fileio.h"
#include "format-table.h"
#include "fs-util.h"
#include "hexdecoct.h"
#include "memory-util.h"
+#include "openssl-util.h"
+#include "parse-util.h"
#include "random-util.h"
#include "sha256.h"
+#include "stat-util.h"
#include "time-util.h"
+#include "tpm2-util.h"
+#include "virt.h"
+#if HAVE_TPM2
static void *libtss2_esys_dl = NULL;
static void *libtss2_rc_dl = NULL;
static void *libtss2_mu_dl = NULL;
@@ -33,15 +36,19 @@ TSS2_RC (*sym_Esys_GetCapability)(ESYS_CONTEXT *esysContext, ESYS_TR shandle1, E
TSS2_RC (*sym_Esys_GetRandom)(ESYS_CONTEXT *esysContext, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, UINT16 bytesRequested, TPM2B_DIGEST **randomBytes) = NULL;
TSS2_RC (*sym_Esys_Initialize)(ESYS_CONTEXT **esys_context, TSS2_TCTI_CONTEXT *tcti, TSS2_ABI_VERSION *abiVersion) = NULL;
TSS2_RC (*sym_Esys_Load)(ESYS_CONTEXT *esysContext, ESYS_TR parentHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_PRIVATE *inPrivate, const TPM2B_PUBLIC *inPublic, ESYS_TR *objectHandle) = NULL;
+TSS2_RC (*sym_Esys_LoadExternal)(ESYS_CONTEXT *esysContext, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_SENSITIVE *inPrivate, const TPM2B_PUBLIC *inPublic, ESYS_TR hierarchy, ESYS_TR *objectHandle);
TSS2_RC (*sym_Esys_PCR_Read)(ESYS_CONTEXT *esysContext, ESYS_TR shandle1,ESYS_TR shandle2, ESYS_TR shandle3, const TPML_PCR_SELECTION *pcrSelectionIn, UINT32 *pcrUpdateCounter, TPML_PCR_SELECTION **pcrSelectionOut, TPML_DIGEST **pcrValues);
+TSS2_RC (*sym_Esys_PolicyAuthorize)(ESYS_CONTEXT *esysContext, ESYS_TR policySession, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_DIGEST *approvedPolicy, const TPM2B_NONCE *policyRef, const TPM2B_NAME *keySign, const TPMT_TK_VERIFIED *checkTicket);
TSS2_RC (*sym_Esys_PolicyAuthValue)(ESYS_CONTEXT *esysContext, ESYS_TR policySession, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3) = NULL;
TSS2_RC (*sym_Esys_PolicyGetDigest)(ESYS_CONTEXT *esysContext, ESYS_TR policySession, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, TPM2B_DIGEST **policyDigest) = NULL;
TSS2_RC (*sym_Esys_PolicyPCR)(ESYS_CONTEXT *esysContext, ESYS_TR policySession, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_DIGEST *pcrDigest, const TPML_PCR_SELECTION *pcrs) = NULL;
TSS2_RC (*sym_Esys_StartAuthSession)(ESYS_CONTEXT *esysContext, ESYS_TR tpmKey, ESYS_TR bind, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_NONCE *nonceCaller, TPM2_SE sessionType, const TPMT_SYM_DEF *symmetric, TPMI_ALG_HASH authHash, ESYS_TR *sessionHandle) = NULL;
TSS2_RC (*sym_Esys_Startup)(ESYS_CONTEXT *esysContext, TPM2_SU startupType) = NULL;
TSS2_RC (*sym_Esys_TRSess_SetAttributes)(ESYS_CONTEXT *esysContext, ESYS_TR session, TPMA_SESSION flags, TPMA_SESSION mask);
+TSS2_RC (*sym_Esys_TR_GetName)(ESYS_CONTEXT *esysContext, ESYS_TR handle, TPM2B_NAME **name);
TSS2_RC (*sym_Esys_TR_SetAuth)(ESYS_CONTEXT *esysContext, ESYS_TR handle, TPM2B_AUTH const *authValue) = NULL;
TSS2_RC (*sym_Esys_Unseal)(ESYS_CONTEXT *esysContext, ESYS_TR itemHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, TPM2B_SENSITIVE_DATA **outData) = NULL;
+TSS2_RC (*sym_Esys_VerifySignature)(ESYS_CONTEXT *esysContext, ESYS_TR keyHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_DIGEST *digest, const TPMT_SIGNATURE *signature, TPMT_TK_VERIFIED **validation);
const char* (*sym_Tss2_RC_Decode)(TSS2_RC rc) = NULL;
@@ -64,15 +71,19 @@ int dlopen_tpm2(void) {
DLSYM_ARG(Esys_GetRandom),
DLSYM_ARG(Esys_Initialize),
DLSYM_ARG(Esys_Load),
+ DLSYM_ARG(Esys_LoadExternal),
DLSYM_ARG(Esys_PCR_Read),
+ DLSYM_ARG(Esys_PolicyAuthorize),
DLSYM_ARG(Esys_PolicyAuthValue),
DLSYM_ARG(Esys_PolicyGetDigest),
DLSYM_ARG(Esys_PolicyPCR),
DLSYM_ARG(Esys_StartAuthSession),
DLSYM_ARG(Esys_Startup),
DLSYM_ARG(Esys_TRSess_SetAttributes),
+ DLSYM_ARG(Esys_TR_GetName),
DLSYM_ARG(Esys_TR_SetAuth),
- DLSYM_ARG(Esys_Unseal));
+ DLSYM_ARG(Esys_Unseal),
+ DLSYM_ARG(Esys_VerifySignature));
if (r < 0)
return r;
@@ -90,13 +101,7 @@ int dlopen_tpm2(void) {
DLSYM_ARG(Tss2_MU_TPM2B_PUBLIC_Unmarshal));
}
-struct tpm2_context {
- ESYS_CONTEXT *esys_context;
- void *tcti_dl;
- TSS2_TCTI_CONTEXT *tcti_context;
-};
-
-static void tpm2_context_destroy(struct tpm2_context *c) {
+void tpm2_context_destroy(struct tpm2_context *c) {
assert(c);
if (c->esys_context)
@@ -117,12 +122,7 @@ static inline void Esys_Finalize_wrapper(ESYS_CONTEXT **c) {
sym_Esys_Finalize(c);
}
-static inline void Esys_Freep(void *p) {
- if (*(void**) p)
- sym_Esys_Free(*(void**) p);
-}
-
-static ESYS_TR flush_context_verbose(ESYS_CONTEXT *c, ESYS_TR handle) {
+ESYS_TR tpm2_flush_context_verbose(ESYS_CONTEXT *c, ESYS_TR handle) {
TSS2_RC rc;
if (!c || handle == ESYS_TR_NONE)
@@ -139,7 +139,7 @@ static ESYS_TR flush_context_verbose(ESYS_CONTEXT *c, ESYS_TR handle) {
return ESYS_TR_NONE;
}
-static int tpm2_init(const char *device, struct tpm2_context *ret) {
+int tpm2_context_init(const char *device, struct tpm2_context *ret) {
_cleanup_(Esys_Finalize_wrapper) ESYS_CONTEXT *c = NULL;
_cleanup_free_ TSS2_TCTI_CONTEXT *tcti = NULL;
_cleanup_(dlclosep) void *dl = NULL;
@@ -407,7 +407,7 @@ static int tpm2_make_primary(
return 0;
}
-static void tpm2_pcr_mask_to_selection(uint32_t mask, uint16_t bank, TPML_PCR_SELECTION *ret) {
+void tpm2_pcr_mask_to_selection(uint32_t mask, uint16_t bank, TPML_PCR_SELECTION *ret) {
assert(ret);
/* We only do 24bit here, as that's what PC TPMs are supposed to support */
@@ -707,17 +707,201 @@ static int tpm2_make_encryption_session(
session = ESYS_TR_NONE;
}
- session = flush_context_verbose(c, session);
+ session = tpm2_flush_context_verbose(c, session);
return 0;
}
-static int tpm2_make_pcr_session(
+static int openssl_pubkey_to_tpm2_pubkey(EVP_PKEY *input, TPM2B_PUBLIC *output) {
+#if OPENSSL_VERSION_MAJOR >= 3
+ _cleanup_(BN_freep) BIGNUM *n = NULL, *e = NULL;
+#else
+ const BIGNUM *n = NULL, *e = NULL;
+ const RSA *rsa = NULL;
+#endif
+ int n_bytes, e_bytes;
+
+ assert(input);
+ assert(output);
+
+ /* Converts an OpenSSL public key to a structure that the TPM chip can process. */
+
+ if (EVP_PKEY_base_id(input) != EVP_PKEY_RSA)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Provided public key is not an RSA key.");
+
+#if OPENSSL_VERSION_MAJOR >= 3
+ if (!EVP_PKEY_get_bn_param(input, OSSL_PKEY_PARAM_RSA_N, &n))
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to get RSA modulus from public key.");
+#else
+ rsa = EVP_PKEY_get0_RSA(input);
+ if (!rsa)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to extract RSA key from public key.");
+
+ n = RSA_get0_n(rsa);
+ if (!n)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to get RSA modulus from public key.");
+#endif
+
+ n_bytes = BN_num_bytes(n);
+ assert_se(n_bytes > 0);
+ if ((size_t) n_bytes > sizeof_field(TPM2B_PUBLIC, publicArea.unique.rsa.buffer))
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "RSA modulus too large for TPM2 public key object.");
+
+#if OPENSSL_VERSION_MAJOR >= 3
+ if (!EVP_PKEY_get_bn_param(input, OSSL_PKEY_PARAM_RSA_E, &e))
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to get RSA exponent from public key.");
+#else
+ e = RSA_get0_e(rsa);
+ if (!e)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to get RSA exponent from public key.");
+#endif
+
+ e_bytes = BN_num_bytes(e);
+ assert_se(e_bytes > 0);
+ if ((size_t) e_bytes > sizeof_field(TPM2B_PUBLIC, publicArea.parameters.rsaDetail.exponent))
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "RSA exponent too large for TPM2 public key object.");
+
+ *output = (TPM2B_PUBLIC) {
+ .size = sizeof(TPMT_PUBLIC),
+ .publicArea = {
+ .type = TPM2_ALG_RSA,
+ .nameAlg = TPM2_ALG_SHA256,
+ .objectAttributes = TPMA_OBJECT_DECRYPT | TPMA_OBJECT_SIGN_ENCRYPT | TPMA_OBJECT_USERWITHAUTH,
+ .parameters.rsaDetail = {
+ .scheme = {
+ .scheme = TPM2_ALG_NULL,
+ .details.anySig.hashAlg = TPM2_ALG_NULL,
+ },
+ .symmetric = {
+ .algorithm = TPM2_ALG_NULL,
+ .mode.sym = TPM2_ALG_NULL,
+ },
+ .keyBits = n_bytes * 8,
+ /* .exponent will be filled in below. */
+ },
+ .unique = {
+ .rsa.size = n_bytes,
+ /* .rsa.buffer will be filled in below. */
+ },
+ },
+ };
+
+ if (BN_bn2bin(n, output->publicArea.unique.rsa.buffer) <= 0)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to convert RSA modulus.");
+
+ if (BN_bn2bin(e, (unsigned char*) &output->publicArea.parameters.rsaDetail.exponent) <= 0)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to convert RSA exponent.");
+
+ return 0;
+}
+
+static int find_signature(
+ JsonVariant *v,
+ uint16_t pcr_bank,
+ uint32_t pcr_mask,
+ EVP_PKEY *pk,
+ const void *policy,
+ size_t policy_size,
+ void *ret_signature,
+ size_t *ret_signature_size) {
+
+ _cleanup_free_ void *fp = NULL;
+ JsonVariant *b, *i;
+ size_t fp_size;
+ const char *k;
+ int r;
+
+ /* Searches for a signature blob in the specified JSON object. Search keys are PCR bank, PCR mask,
+ * public key, and policy digest. */
+
+ if (!json_variant_is_object(v))
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Signature is not a JSON object.");
+
+ k = tpm2_pcr_bank_to_string(pcr_bank);
+ if (!k)
+ return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Don't know PCR bank %" PRIu16, pcr_bank);
+
+ /* First, find field by bank */
+ b = json_variant_by_key(v, k);
+ if (!b)
+ return log_error_errno(SYNTHETIC_ERRNO(ENXIO), "Signature lacks data for PCR bank '%s'.", k);
+
+ if (!json_variant_is_array(b))
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Bank data is not a JSON array.");
+
+ /* Now iterate through all signatures known for this bank */
+ JSON_VARIANT_ARRAY_FOREACH(i, b) {
+ _cleanup_free_ void *fpj_data = NULL, *polj_data = NULL;
+ JsonVariant *maskj, *fpj, *sigj, *polj;
+ size_t fpj_size, polj_size;
+ uint32_t parsed_mask;
+
+ if (!json_variant_is_object(i))
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Bank data element is not a JSON object");
+
+ /* Check if the PCR mask matches our expectations */
+ maskj = json_variant_by_key(i, "pcrs");
+ if (!maskj)
+ continue;
+
+ r = tpm2_parse_pcr_json_array(maskj, &parsed_mask);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse JSON PCR mask");
+
+ if (parsed_mask != pcr_mask)
+ continue; /* Not for this PCR mask */
+
+ /* Then check if this is for the public key we operate with */
+ fpj = json_variant_by_key(i, "pkfp");
+ if (!fpj)
+ continue;
+
+ r = json_variant_unhex(fpj, &fpj_data, &fpj_size);
+ if (r < 0)
+ return log_error_errno(r, "Failed to decode fingerprint in JSON data: %m");
+
+ if (!fp) {
+ r = pubkey_fingerprint(pk, EVP_sha256(), &fp, &fp_size);
+ if (r < 0)
+ return log_error_errno(r, "Failed to calculate public key fingerprint: %m");
+ }
+
+ if (memcmp_nn(fp, fp_size, fpj_data, fpj_size) != 0)
+ continue; /* Not for this public key */
+
+ /* Finally, check if this is for the PCR policy we expect this to be */
+ polj = json_variant_by_key(i, "pol");
+ if (!polj)
+ continue;
+
+ r = json_variant_unhex(polj, &polj_data, &polj_size);
+ if (r < 0)
+ return log_error_errno(r, "Failed to decode policy hash JSON data: %m");
+
+ if (memcmp_nn(policy, policy_size, polj_data, polj_size) != 0)
+ continue;
+
+ /* This entry matches all our expectations, now return the signature included in it */
+ sigj = json_variant_by_key(i, "sig");
+ if (!sigj)
+ continue;
+
+ return json_variant_unbase64(sigj, ret_signature, ret_signature_size);
+ }
+
+ return log_error_errno(SYNTHETIC_ERRNO(ENXIO), "Couldn't find signature for this PCR bank, PCR index and public key.");
+}
+
+static int tpm2_make_policy_session(
ESYS_CONTEXT *c,
ESYS_TR primary,
ESYS_TR parent_session,
TPM2_SE session_type,
- uint32_t pcr_mask,
+ uint32_t hash_pcr_mask,
uint16_t pcr_bank, /* If UINT16_MAX, pick best bank automatically, otherwise specify bank explicitly. */
+ const void *pubkey,
+ size_t pubkey_size,
+ uint32_t pubkey_pcr_mask,
+ JsonVariant *signature_json,
bool use_pin,
ESYS_TR *ret_session,
TPM2B_DIGEST **ret_policy_digest,
@@ -729,33 +913,44 @@ static int tpm2_make_pcr_session(
.mode.aes = TPM2_ALG_CFB,
};
_cleanup_(Esys_Freep) TPM2B_DIGEST *policy_digest = NULL;
- TPML_PCR_SELECTION pcr_selection;
- ESYS_TR session = ESYS_TR_NONE;
+ ESYS_TR session = ESYS_TR_NONE, pubkey_handle = ESYS_TR_NONE;
+ _cleanup_(EVP_PKEY_freep) EVP_PKEY *pk = NULL;
TSS2_RC rc;
int r;
assert(c);
+ assert(pubkey || pubkey_size == 0);
+ assert(pubkey_pcr_mask == 0 || pubkey_size > 0);
log_debug("Starting authentication session.");
- if (pcr_bank != UINT16_MAX) {
- r = tpm2_pcr_mask_good(c, pcr_bank, pcr_mask);
- if (r < 0)
- return r;
- if (r == 0)
- log_notice("Selected TPM2 PCRs are not initialized on this system, most likely due to a firmware issue. PCR policy is effectively not enforced. Proceeding anyway.");
+ if ((hash_pcr_mask | pubkey_pcr_mask) != 0) {
+ /* We are told to configure a PCR policy of some form, let's determine/validate the PCR bank to use. */
- tpm2_pcr_mask_to_selection(pcr_mask, pcr_bank, &pcr_selection);
- } else {
- TPMI_ALG_HASH h;
+ if (pcr_bank != UINT16_MAX) {
+ r = tpm2_pcr_mask_good(c, pcr_bank, hash_pcr_mask|pubkey_pcr_mask);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ log_warning("Selected TPM2 PCRs are not initialized on this system, most likely due to a firmware issue. PCR policy is effectively not enforced. Proceeding anyway.");
+ } else {
+ /* No bank configured, pick automatically. Some TPM2 devices only can do SHA1. If we
+ * detect that use that, but preferably use SHA256 */
+ r = tpm2_get_best_pcr_bank(c, hash_pcr_mask|pubkey_pcr_mask, &pcr_bank);
+ if (r < 0)
+ return r;
+ }
+ }
- /* No bank configured, pick automatically. Some TPM2 devices only can do SHA1. If we detect
- * that use that, but preferably use SHA256 */
- r = tpm2_get_best_pcr_bank(c, pcr_mask, &h);
- if (r < 0)
- return r;
+ if (pubkey_size > 0) {
+ /* If a pubkey is specified, load it to validate it, even if the PCR mask for this is actually zero, and we are thus not going to use it. */
+ _cleanup_fclose_ FILE *f = fmemopen((void*) pubkey, pubkey_size, "r");
+ if (!f)
+ return log_oom();
- tpm2_pcr_mask_to_selection(pcr_mask, h, &pcr_selection);
+ pk = PEM_read_PUBKEY(f, NULL, NULL, NULL);
+ if (!pk)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse PEM public key.");
}
rc = sym_Esys_StartAuthSession(
@@ -774,23 +969,180 @@ static int tpm2_make_pcr_session(
return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
"Failed to open session in TPM: %s", sym_Tss2_RC_Decode(rc));
- log_debug("Configuring PCR policy.");
+ if (pubkey_pcr_mask != 0) {
+ log_debug("Configuring public key based PCR policy.");
- rc = sym_Esys_PolicyPCR(
- c,
- session,
- ESYS_TR_NONE,
- ESYS_TR_NONE,
- ESYS_TR_NONE,
- NULL,
- &pcr_selection);
- if (rc != TSS2_RC_SUCCESS) {
- r = log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
- "Failed to add PCR policy to TPM: %s", sym_Tss2_RC_Decode(rc));
- goto finish;
+ /* First: load public key into the TPM */
+ TPM2B_PUBLIC pubkey_tpm2;
+ r = openssl_pubkey_to_tpm2_pubkey(pk, &pubkey_tpm2);
+ if (r < 0)
+ goto finish;
+
+ rc = sym_Esys_LoadExternal(
+ c,
+ ESYS_TR_NONE,
+ ESYS_TR_NONE,
+ ESYS_TR_NONE,
+ NULL,
+ &pubkey_tpm2,
+ TPM2_RH_OWNER,
+ &pubkey_handle);
+ if (rc != TSS2_RC_SUCCESS) {
+ r = log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
+ "Failed to load public key into TPM: %s", sym_Tss2_RC_Decode(rc));
+ goto finish;
+ }
+
+ /* Acquire the "name" of what we just loaded */
+ _cleanup_(Esys_Freep) TPM2B_NAME *pubkey_name = NULL;
+ rc = sym_Esys_TR_GetName(
+ c,
+ pubkey_handle,
+ &pubkey_name);
+ if (rc != TSS2_RC_SUCCESS) {
+ r = log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
+ "Failed to get name of public key from TPM: %s", sym_Tss2_RC_Decode(rc));
+ goto finish;
+ }
+
+ /* Put together the PCR policy we want to use */
+ TPML_PCR_SELECTION pcr_selection;
+ tpm2_pcr_mask_to_selection(pubkey_pcr_mask, pcr_bank, &pcr_selection);
+ rc = sym_Esys_PolicyPCR(
+ c,
+ session,
+ ESYS_TR_NONE,
+ ESYS_TR_NONE,
+ ESYS_TR_NONE,
+ NULL,
+ &pcr_selection);
+ if (rc != TSS2_RC_SUCCESS) {
+ r = log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
+ "Failed to add PCR policy to TPM: %s", sym_Tss2_RC_Decode(rc));
+ goto finish;
+ }
+
+ /* Get the policy hash of the PCR policy */
+ _cleanup_(Esys_Freep) TPM2B_DIGEST *approved_policy = NULL;
+ rc = sym_Esys_PolicyGetDigest(
+ c,
+ session,
+ ESYS_TR_NONE,
+ ESYS_TR_NONE,
+ ESYS_TR_NONE,
+ &approved_policy);
+ if (rc != TSS2_RC_SUCCESS) {
+ r = log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
+ "Failed to get policy digest from TPM: %s", sym_Tss2_RC_Decode(rc));
+ goto finish;
+ }
+
+ /* When we are unlocking and have a signature, let's pass it to the TPM */
+ _cleanup_(Esys_Freep) TPMT_TK_VERIFIED *check_ticket_buffer = NULL;
+ const TPMT_TK_VERIFIED *check_ticket;
+ if (signature_json) {
+ _cleanup_free_ void *signature_raw = NULL;
+ size_t signature_size;
+
+ r = find_signature(
+ signature_json,
+ pcr_bank,
+ pubkey_pcr_mask,
+ pk,
+ approved_policy->buffer,
+ approved_policy->size,
+ &signature_raw,
+ &signature_size);
+ if (r < 0)
+ goto finish;
+
+ /* TPM2_VerifySignature() will only verify the RSA part of the RSA+SHA256 signature,
+ * hence we need to do the SHA256 part outselves, first */
+ TPM2B_DIGEST signature_hash = {
+ .size = SHA256_DIGEST_SIZE,
+ };
+ assert(sizeof(signature_hash.buffer) >= SHA256_DIGEST_SIZE);
+ sha256_direct(approved_policy->buffer, approved_policy->size, signature_hash.buffer);
+
+ TPMT_SIGNATURE policy_signature = {
+ .sigAlg = TPM2_ALG_RSASSA,
+ .signature.rsassa = {
+ .hash = TPM2_ALG_SHA256,
+ .sig.size = signature_size,
+ },
+ };
+ if (signature_size > sizeof(policy_signature.signature.rsassa.sig.buffer)) {
+ r = log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Signature larger than buffer.");
+ goto finish;
+ }
+ memcpy(policy_signature.signature.rsassa.sig.buffer, signature_raw, signature_size);
+
+ rc = sym_Esys_VerifySignature(
+ c,
+ pubkey_handle,
+ ESYS_TR_NONE,
+ ESYS_TR_NONE,
+ ESYS_TR_NONE,
+ &signature_hash,
+ &policy_signature,
+ &check_ticket_buffer);
+ if (rc != TSS2_RC_SUCCESS) {
+ r = log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
+ "Failed to validate signature in TPM: %s", sym_Tss2_RC_Decode(rc));
+ goto finish;
+ }
+
+ check_ticket = check_ticket_buffer;
+ } else {
+ /* When enrolling, we pass a NULL ticket */
+ static const TPMT_TK_VERIFIED check_ticket_null = {
+ .tag = TPM2_ST_VERIFIED,
+ .hierarchy = TPM2_RH_OWNER,
+ };
+
+ check_ticket = &check_ticket_null;
+ }
+
+ rc = sym_Esys_PolicyAuthorize(
+ c,
+ session,
+ ESYS_TR_NONE,
+ ESYS_TR_NONE,
+ ESYS_TR_NONE,
+ approved_policy,
+ /* policyRef= */ &(const TPM2B_NONCE) {},
+ pubkey_name,
+ check_ticket);
+ if (rc != TSS2_RC_SUCCESS) {
+ r = log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
+ "Failed to push Authorize policy into TPM: %s", sym_Tss2_RC_Decode(rc));
+ goto finish;
+ }
+ }
+
+ if (hash_pcr_mask != 0) {
+ log_debug("Configuring hash-based PCR policy.");
+
+ TPML_PCR_SELECTION pcr_selection;
+ tpm2_pcr_mask_to_selection(hash_pcr_mask, pcr_bank, &pcr_selection);
+ rc = sym_Esys_PolicyPCR(
+ c,
+ session,
+ ESYS_TR_NONE,
+ ESYS_TR_NONE,
+ ESYS_TR_NONE,
+ NULL,
+ &pcr_selection);
+ if (rc != TSS2_RC_SUCCESS) {
+ r = log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
+ "Failed to add PCR policy to TPM: %s", sym_Tss2_RC_Decode(rc));
+ goto finish;
+ }
}
if (use_pin) {
+ log_debug("Configuring PIN policy.");
+
rc = sym_Esys_PolicyAuthValue(
c,
session,
@@ -844,27 +1196,30 @@ static int tpm2_make_pcr_session(
*ret_policy_digest = TAKE_PTR(policy_digest);
if (ret_pcr_bank)
- *ret_pcr_bank = pcr_selection.pcrSelections[0].hash;
+ *ret_pcr_bank = pcr_bank;
r = 0;
finish:
- session = flush_context_verbose(c, session);
+ session = tpm2_flush_context_verbose(c, session);
+ pubkey_handle = tpm2_flush_context_verbose(c, pubkey_handle);
return r;
}
-int tpm2_seal(
- const char *device,
- uint32_t pcr_mask,
- const char *pin,
- void **ret_secret,
- size_t *ret_secret_size,
- void **ret_blob,
- size_t *ret_blob_size,
- void **ret_pcr_hash,
- size_t *ret_pcr_hash_size,
- uint16_t *ret_pcr_bank,
- uint16_t *ret_primary_alg) {
+int tpm2_seal(const char *device,
+ uint32_t hash_pcr_mask,
+ const void *pubkey,
+ const size_t pubkey_size,
+ uint32_t pubkey_pcr_mask,
+ const char *pin,
+ void **ret_secret,
+ size_t *ret_secret_size,
+ void **ret_blob,
+ size_t *ret_blob_size,
+ void **ret_pcr_hash,
+ size_t *ret_pcr_hash_size,
+ uint16_t *ret_pcr_bank,
+ uint16_t *ret_primary_alg) {
_cleanup_(tpm2_context_destroy) struct tpm2_context c = {};
_cleanup_(Esys_Freep) TPM2B_DIGEST *policy_digest = NULL;
@@ -883,6 +1238,8 @@ int tpm2_seal(
TSS2_RC rc;
int r;
+ assert(pubkey || pubkey_size == 0);
+
assert(ret_secret);
assert(ret_secret_size);
assert(ret_blob);
@@ -891,7 +1248,8 @@ int tpm2_seal(
assert(ret_pcr_hash_size);
assert(ret_pcr_bank);
- assert(TPM2_PCR_MASK_VALID(pcr_mask));
+ assert(TPM2_PCR_MASK_VALID(hash_pcr_mask));
+ assert(TPM2_PCR_MASK_VALID(pubkey_pcr_mask));
/* So here's what we do here: we connect to the TPM2 chip. It persistently contains a "seed" key that
* is randomized when the TPM2 is first initialized or reset and remains stable across boots. We
@@ -911,7 +1269,7 @@ int tpm2_seal(
start = now(CLOCK_MONOTONIC);
- r = tpm2_init(device, &c);
+ r = tpm2_context_init(device, &c);
if (r < 0)
return r;
@@ -924,13 +1282,16 @@ int tpm2_seal(
if (r < 0)
goto finish;
- r = tpm2_make_pcr_session(
+ r = tpm2_make_policy_session(
c.esys_context,
primary,
session,
TPM2_SE_TRIAL,
- pcr_mask,
+ hash_pcr_mask,
/* pcr_bank= */ UINT16_MAX,
+ pubkey, pubkey_size,
+ pubkey_pcr_mask,
+ /* signature_json= */ NULL,
!!pin,
/* ret_session= */ NULL,
&policy_digest,
@@ -1058,21 +1419,24 @@ int tpm2_seal(
finish:
explicit_bzero_safe(&hmac_sensitive, sizeof(hmac_sensitive));
- primary = flush_context_verbose(c.esys_context, primary);
- session = flush_context_verbose(c.esys_context, session);
+ primary = tpm2_flush_context_verbose(c.esys_context, primary);
+ session = tpm2_flush_context_verbose(c.esys_context, session);
return r;
}
-int tpm2_unseal(
- const char *device,
- uint32_t pcr_mask,
+int tpm2_unseal(const char *device,
+ uint32_t hash_pcr_mask,
uint16_t pcr_bank,
+ const void *pubkey,
+ size_t pubkey_size,
+ uint32_t pubkey_pcr_mask,
+ JsonVariant *signature,
+ const char *pin,
uint16_t primary_alg,
const void *blob,
size_t blob_size,
const void *known_policy_hash,
size_t known_policy_hash_size,
- const char *pin,
void **ret_secret,
size_t *ret_secret_size) {
@@ -1092,10 +1456,12 @@ int tpm2_unseal(
assert(blob);
assert(blob_size > 0);
assert(known_policy_hash_size == 0 || known_policy_hash);
+ assert(pubkey_size == 0 || pubkey);
assert(ret_secret);
assert(ret_secret_size);
- assert(TPM2_PCR_MASK_VALID(pcr_mask));
+ assert(TPM2_PCR_MASK_VALID(hash_pcr_mask));
+ assert(TPM2_PCR_MASK_VALID(pubkey_pcr_mask));
r = dlopen_tpm2();
if (r < 0)
@@ -1125,7 +1491,7 @@ int tpm2_unseal(
return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE),
"Failed to unmarshal public key: %s", sym_Tss2_RC_Decode(rc));
- r = tpm2_init(device, &c);
+ r = tpm2_context_init(device, &c);
if (r < 0)
return r;
@@ -1169,13 +1535,16 @@ int tpm2_unseal(
if (r < 0)
goto finish;
- r = tpm2_make_pcr_session(
+ r = tpm2_make_policy_session(
c.esys_context,
primary,
hmac_session,
TPM2_SE_POLICY,
- pcr_mask,
+ hash_pcr_mask,
pcr_bank,
+ pubkey, pubkey_size,
+ pubkey_pcr_mask,
+ signature,
!!pin,
&session,
&policy_digest,
@@ -1222,9 +1591,9 @@ int tpm2_unseal(
r = 0;
finish:
- primary = flush_context_verbose(c.esys_context, primary);
- session = flush_context_verbose(c.esys_context, session);
- hmac_key = flush_context_verbose(c.esys_context, hmac_key);
+ primary = tpm2_flush_context_verbose(c.esys_context, primary);
+ session = tpm2_flush_context_verbose(c.esys_context, session);
+ hmac_key = tpm2_flush_context_verbose(c.esys_context, hmac_key);
return r;
}
@@ -1453,8 +1822,11 @@ int tpm2_parse_pcr_json_array(JsonVariant *v, uint32_t *ret) {
int tpm2_make_luks2_json(
int keyslot,
- uint32_t pcr_mask,
+ uint32_t hash_pcr_mask,
uint16_t pcr_bank,
+ const void *pubkey,
+ size_t pubkey_size,
+ uint32_t pubkey_pcr_mask,
uint16_t primary_alg,
const void *blob,
size_t blob_size,
@@ -1463,31 +1835,43 @@ int tpm2_make_luks2_json(
TPM2Flags flags,
JsonVariant **ret) {
- _cleanup_(json_variant_unrefp) JsonVariant *v = NULL, *a = NULL;
+ _cleanup_(json_variant_unrefp) JsonVariant *v = NULL, *hmj = NULL, *pkmj = NULL;
_cleanup_free_ char *keyslot_as_string = NULL;
int r;
assert(blob || blob_size == 0);
assert(policy_hash || policy_hash_size == 0);
+ assert(pubkey || pubkey_size == 0);
if (asprintf(&keyslot_as_string, "%i", keyslot) < 0)
return -ENOMEM;
- r = tpm2_make_pcr_json_array(pcr_mask, &a);
+ r = tpm2_make_pcr_json_array(hash_pcr_mask, &hmj);
if (r < 0)
return r;
+ if (pubkey_pcr_mask != 0) {
+ r = tpm2_make_pcr_json_array(pubkey_pcr_mask, &pkmj);
+ if (r < 0)
+ return r;
+ }
+
+ /* Note: We made the mistake of using "-" in the field names, which isn't particular compatible with
+ * other programming languages. Let's not make things worse though, i.e. future additions to the JSON
+ * object should use "_" rather than "-" in field names. */
+
r = json_build(&v,
JSON_BUILD_OBJECT(
JSON_BUILD_PAIR("type", JSON_BUILD_CONST_STRING("systemd-tpm2")),
JSON_BUILD_PAIR("keyslots", JSON_BUILD_ARRAY(JSON_BUILD_STRING(keyslot_as_string))),
JSON_BUILD_PAIR("tpm2-blob", JSON_BUILD_BASE64(blob, blob_size)),
- JSON_BUILD_PAIR("tpm2-pcrs", JSON_BUILD_VARIANT(a)),
+ JSON_BUILD_PAIR("tpm2-pcrs", JSON_BUILD_VARIANT(hmj)),
JSON_BUILD_PAIR_CONDITION(!!tpm2_pcr_bank_to_string(pcr_bank), "tpm2-pcr-bank", JSON_BUILD_STRING(tpm2_pcr_bank_to_string(pcr_bank))),
JSON_BUILD_PAIR_CONDITION(!!tpm2_primary_alg_to_string(primary_alg), "tpm2-primary-alg", JSON_BUILD_STRING(tpm2_primary_alg_to_string(primary_alg))),
JSON_BUILD_PAIR("tpm2-policy-hash", JSON_BUILD_HEX(policy_hash, policy_hash_size)),
- JSON_BUILD_PAIR("tpm2-pin", JSON_BUILD_BOOLEAN(flags & TPM2_FLAGS_USE_PIN)))
- );
+ JSON_BUILD_PAIR("tpm2-pin", JSON_BUILD_BOOLEAN(flags & TPM2_FLAGS_USE_PIN)),
+ JSON_BUILD_PAIR_CONDITION(pubkey_pcr_mask != 0, "tpm2_pubkey_pcrs", JSON_BUILD_VARIANT(pkmj)),
+ JSON_BUILD_PAIR_CONDITION(pubkey_pcr_mask != 0, "tpm2_pubkey", JSON_BUILD_BASE64(pubkey, pubkey_size))));
if (r < 0)
return r;
@@ -1497,6 +1881,150 @@ int tpm2_make_luks2_json(
return keyslot;
}
+int tpm2_parse_luks2_json(
+ JsonVariant *v,
+ int *ret_keyslot,
+ uint32_t *ret_hash_pcr_mask,
+ uint16_t *ret_pcr_bank,
+ void **ret_pubkey,
+ size_t *ret_pubkey_size,
+ uint32_t *ret_pubkey_pcr_mask,
+ uint16_t *ret_primary_alg,
+ void **ret_blob,
+ size_t *ret_blob_size,
+ void **ret_policy_hash,
+ size_t *ret_policy_hash_size,
+ TPM2Flags *ret_flags) {
+
+ _cleanup_free_ void *blob = NULL, *policy_hash = NULL, *pubkey = NULL;
+ size_t blob_size = 0, policy_hash_size = 0, pubkey_size = 0;
+ uint32_t hash_pcr_mask = 0, pubkey_pcr_mask = 0;
+ uint16_t primary_alg = TPM2_ALG_ECC; /* ECC was the only supported algorithm in systemd < 250, use that as implied default, for compatibility */
+ uint16_t pcr_bank = UINT16_MAX; /* default: pick automatically */
+ int r, keyslot = -1;
+ TPM2Flags flags = 0;
+ JsonVariant *w;
+
+ assert(v);
+
+ if (ret_keyslot) {
+ keyslot = cryptsetup_get_keyslot_from_token(v);
+ if (keyslot < 0) {
+ /* Return a recognizable error when parsing this field, so that callers can handle parsing
+ * errors of the keyslots field gracefully, since it's not 'owned' by us, but by the LUKS2
+ * spec */
+ log_debug_errno(keyslot, "Failed to extract keyslot index from TPM2 JSON data token, skipping: %m");
+ return -EUCLEAN;
+ }
+ }
+
+ w = json_variant_by_key(v, "tpm2-pcrs");
+ if (!w)
+ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "TPM2 token data lacks 'tpm2-pcrs' field.");
+
+ r = tpm2_parse_pcr_json_array(w, &hash_pcr_mask);
+ if (r < 0)
+ return log_debug_errno(r, "Failed to parse TPM2 PCR mask: %m");
+
+ /* The bank field is optional, since it was added in systemd 250 only. Before the bank was hardcoded
+ * to SHA256. */
+ w = json_variant_by_key(v, "tpm2-pcr-bank");
+ if (w) {
+ /* The PCR bank field is optional */
+
+ if (!json_variant_is_string(w))
+ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "TPM2 PCR bank is not a string.");
+
+ r = tpm2_pcr_bank_from_string(json_variant_string(w));
+ if (r < 0)
+ return log_debug_errno(r, "TPM2 PCR bank invalid or not supported: %s", json_variant_string(w));
+
+ pcr_bank = r;
+ }
+
+ /* The primary key algorithm field is optional, since it was also added in systemd 250 only. Before
+ * the algorithm was hardcoded to ECC. */
+ w = json_variant_by_key(v, "tpm2-primary-alg");
+ if (w) {
+ /* The primary key algorithm is optional */
+
+ if (!json_variant_is_string(w))
+ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "TPM2 primary key algorithm is not a string.");
+
+ r = tpm2_primary_alg_from_string(json_variant_string(w));
+ if (r < 0)
+ return log_debug_errno(r, "TPM2 primary key algorithm invalid or not supported: %s", json_variant_string(w));
+
+ primary_alg = r;
+ }
+
+ w = json_variant_by_key(v, "tpm2-blob");
+ if (!w)
+ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "TPM2 token data lacks 'tpm2-blob' field.");
+
+ r = json_variant_unbase64(w, &blob, &blob_size);
+ if (r < 0)
+ return log_debug_errno(r, "Invalid base64 data in 'tpm2-blob' field.");
+
+ w = json_variant_by_key(v, "tpm2-policy-hash");
+ if (!w)
+ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "TPM2 token data lacks 'tpm2-policy-hash' field.");
+
+ r = json_variant_unhex(w, &policy_hash, &policy_hash_size);
+ if (r < 0)
+ return log_debug_errno(r, "Invalid base64 data in 'tpm2-policy-hash' field.");
+
+ w = json_variant_by_key(v, "tpm2-pin");
+ if (w) {
+ if (!json_variant_is_boolean(w))
+ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "TPM2 PIN policy is not a boolean.");
+
+ SET_FLAG(flags, TPM2_FLAGS_USE_PIN, json_variant_boolean(w));
+ }
+
+ w = json_variant_by_key(v, "tpm2_pubkey_pcrs");
+ if (w) {
+ r = tpm2_parse_pcr_json_array(w, &pubkey_pcr_mask);
+ if (r < 0)
+ return r;
+ }
+
+ w = json_variant_by_key(v, "tpm2_pubkey");
+ if (w) {
+ r = json_variant_unbase64(w, &pubkey, &pubkey_size);
+ if (r < 0)
+ return log_debug_errno(r, "Failed to decode PCR public key.");
+ } else if (pubkey_pcr_mask != 0)
+ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Public key PCR mask set, but not public key included in JSON data, refusing.");
+
+ if (ret_keyslot)
+ *ret_keyslot = keyslot;
+ if (ret_hash_pcr_mask)
+ *ret_hash_pcr_mask = hash_pcr_mask;
+ if (ret_pcr_bank)
+ *ret_pcr_bank = pcr_bank;
+ if (ret_pubkey)
+ *ret_pubkey = TAKE_PTR(pubkey);
+ if (ret_pubkey_size)
+ *ret_pubkey_size = pubkey_size;
+ if (ret_pubkey_pcr_mask)
+ *ret_pubkey_pcr_mask = pubkey_pcr_mask;
+ if (ret_primary_alg)
+ *ret_primary_alg = primary_alg;
+ if (ret_blob)
+ *ret_blob = TAKE_PTR(blob);
+ if (ret_blob_size)
+ *ret_blob_size = blob_size;
+ if (ret_policy_hash)
+ *ret_policy_hash = TAKE_PTR(policy_hash);
+ if (ret_policy_hash_size)
+ *ret_policy_hash_size = policy_hash_size;
+ if (ret_flags)
+ *ret_flags = flags;
+
+ return 0;
+}
+
const char *tpm2_pcr_bank_to_string(uint16_t bank) {
if (bank == TPM2_ALG_SHA1)
return "sha1";
@@ -1588,3 +2116,66 @@ int tpm2_parse_pcr_argument(const char *arg, uint32_t *mask) {
return 0;
}
+
+int tpm2_load_pcr_signature(const char *path, JsonVariant **ret) {
+ _cleanup_free_ char *discovered_path = NULL;
+ _cleanup_fclose_ FILE *f = NULL;
+ int r;
+
+ /* Tries to load a JSON PCR signature file. Takes an absolute path, a simple file name or NULL. In
+ * the latter two cases searches in /etc/, /usr/lib/, /run/, as usual. */
+
+ if (!path)
+ path = "tpm2-pcr-signature.json";
+
+ r = search_and_fopen(path, "re", NULL, (const char**) CONF_PATHS_STRV("systemd"), &f, &discovered_path);
+ if (r < 0)
+ return log_debug_errno(r, "Failed to find TPM PCR signature file '%s': %m", path);
+
+ r = json_parse_file(f, discovered_path, 0, ret, NULL, NULL);
+ if (r < 0)
+ return log_debug_errno(r, "Failed to parse TPM PCR signature JSON object '%s': %m", discovered_path);
+
+ return 0;
+}
+
+int tpm2_load_pcr_public_key(const char *path, void **ret_pubkey, size_t *ret_pubkey_size) {
+ _cleanup_free_ char *discovered_path = NULL;
+ _cleanup_fclose_ FILE *f = NULL;
+ int r;
+
+ /* Tries to load a PCR public key file. Takes an absolute path, a simple file name or NULL. In the
+ * latter two cases searches in /etc/, /usr/lib/, /run/, as usual. */
+
+ if (!path)
+ path = "tpm2-pcr-public-key.pem";
+
+ r = search_and_fopen(path, "re", NULL, (const char**) CONF_PATHS_STRV("systemd"), &f, &discovered_path);
+ if (r < 0)
+ return log_debug_errno(r, "Failed to find TPM PCR public key file '%s': %m", path);
+
+ r = read_full_stream(f, (char**) ret_pubkey, ret_pubkey_size);
+ if (r < 0)
+ return log_debug_errno(r, "Failed to load TPM PCR public key PEM file '%s': %m", discovered_path);
+
+ return 0;
+}
+
+int pcr_mask_to_string(uint32_t mask, char **ret) {
+ _cleanup_free_ char *buf = NULL;
+ int r;
+
+ assert(ret);
+
+ for (unsigned i = 0; i < TPM2_PCRS_MAX; i++) {
+ if (!(mask & (UINT32_C(1) << i)))
+ continue;
+
+ r = strextendf_with_separator(&buf, "+", "%u", i);
+ if (r < 0)
+ return r;
+ }
+
+ *ret = TAKE_PTR(buf);
+ return 0;
+}
diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h
index 4030f2a804..cd6bcc91b3 100644
--- a/src/shared/tpm2-util.h
+++ b/src/shared/tpm2-util.h
@@ -25,15 +25,19 @@ extern TSS2_RC (*sym_Esys_GetCapability)(ESYS_CONTEXT *esysContext, ESYS_TR shan
extern TSS2_RC (*sym_Esys_GetRandom)(ESYS_CONTEXT *esysContext, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, UINT16 bytesRequested, TPM2B_DIGEST **randomBytes);
extern TSS2_RC (*sym_Esys_Initialize)(ESYS_CONTEXT **esys_context, TSS2_TCTI_CONTEXT *tcti, TSS2_ABI_VERSION *abiVersion);
extern TSS2_RC (*sym_Esys_Load)(ESYS_CONTEXT *esysContext, ESYS_TR parentHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_PRIVATE *inPrivate, const TPM2B_PUBLIC *inPublic, ESYS_TR *objectHandle);
+extern TSS2_RC (*sym_Esys_LoadExternal)(ESYS_CONTEXT *esysContext, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_SENSITIVE *inPrivate, const TPM2B_PUBLIC *inPublic, ESYS_TR hierarchy, ESYS_TR *objectHandle);
extern TSS2_RC (*sym_Esys_PCR_Read)(ESYS_CONTEXT *esysContext, ESYS_TR shandle1,ESYS_TR shandle2, ESYS_TR shandle3, const TPML_PCR_SELECTION *pcrSelectionIn, UINT32 *pcrUpdateCounter, TPML_PCR_SELECTION **pcrSelectionOut, TPML_DIGEST **pcrValues);
+extern TSS2_RC (*sym_Esys_PolicyAuthorize)(ESYS_CONTEXT *esysContext, ESYS_TR policySession, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_DIGEST *approvedPolicy, const TPM2B_NONCE *policyRef, const TPM2B_NAME *keySign, const TPMT_TK_VERIFIED *checkTicket);
extern TSS2_RC (*sym_Esys_PolicyAuthValue)(ESYS_CONTEXT *esysContext, ESYS_TR policySession, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3);
extern TSS2_RC (*sym_Esys_PolicyGetDigest)(ESYS_CONTEXT *esysContext, ESYS_TR policySession, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, TPM2B_DIGEST **policyDigest);
extern TSS2_RC (*sym_Esys_PolicyPCR)(ESYS_CONTEXT *esysContext, ESYS_TR policySession, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_DIGEST *pcrDigest, const TPML_PCR_SELECTION *pcrs);
extern TSS2_RC (*sym_Esys_StartAuthSession)(ESYS_CONTEXT *esysContext, ESYS_TR tpmKey, ESYS_TR bind, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_NONCE *nonceCaller, TPM2_SE sessionType, const TPMT_SYM_DEF *symmetric, TPMI_ALG_HASH authHash, ESYS_TR *sessionHandle);
extern TSS2_RC (*sym_Esys_Startup)(ESYS_CONTEXT *esysContext, TPM2_SU startupType);
extern TSS2_RC (*sym_Esys_TRSess_SetAttributes)(ESYS_CONTEXT *esysContext, ESYS_TR session, TPMA_SESSION flags, TPMA_SESSION mask);
+extern TSS2_RC (*sym_Esys_TR_GetName)(ESYS_CONTEXT *esysContext, ESYS_TR handle, TPM2B_NAME **name);
extern TSS2_RC (*sym_Esys_TR_SetAuth)(ESYS_CONTEXT *esysContext, ESYS_TR handle, TPM2B_AUTH const *authValue);
extern TSS2_RC (*sym_Esys_Unseal)(ESYS_CONTEXT *esysContext, ESYS_TR itemHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, TPM2B_SENSITIVE_DATA **outData);
+extern TSS2_RC (*sym_Esys_VerifySignature)(ESYS_CONTEXT *esysContext, ESYS_TR keyHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_DIGEST *digest, const TPMT_SIGNATURE *signature, TPMT_TK_VERIFIED **validation);
extern const char* (*sym_Tss2_RC_Decode)(TSS2_RC rc);
@@ -44,11 +48,31 @@ extern TSS2_RC (*sym_Tss2_MU_TPM2B_PUBLIC_Unmarshal)(uint8_t const buffer[], siz
int dlopen_tpm2(void);
-int tpm2_seal(const char *device, uint32_t pcr_mask, const char *pin, void **ret_secret, size_t *ret_secret_size, void **ret_blob, size_t *ret_blob_size, void **ret_pcr_hash, size_t *ret_pcr_hash_size, uint16_t *ret_pcr_bank, uint16_t *ret_primary_alg);
-int tpm2_unseal(const char *device, uint32_t pcr_mask, uint16_t pcr_bank, uint16_t primary_alg, const void *blob, size_t blob_size, const void *pcr_hash, size_t pcr_hash_size, const char *pin, void **ret_secret, size_t *ret_secret_size);
+int tpm2_seal(const char *device, uint32_t hash_pcr_mask, const void *pubkey, size_t pubkey_size, uint32_t pubkey_pcr_mask, const char *pin, void **ret_secret, size_t *ret_secret_size, void **ret_blob, size_t *ret_blob_size, void **ret_pcr_hash, size_t *ret_pcr_hash_size, uint16_t *ret_pcr_bank, uint16_t *ret_primary_alg);
+int tpm2_unseal(const char *device, uint32_t hash_pcr_mask, uint16_t pcr_bank, const void *pubkey, size_t pubkey_size, uint32_t pubkey_pcr_mask, JsonVariant *signature, const char *pin, uint16_t primary_alg, const void *blob, size_t blob_size, const void *policy_hash, size_t policy_hash_size, void **ret_secret, size_t *ret_secret_size);
+struct tpm2_context {
+ void *tcti_dl;
+ TSS2_TCTI_CONTEXT *tcti_context;
+ ESYS_CONTEXT *esys_context;
+};
+
+ESYS_TR tpm2_flush_context_verbose(ESYS_CONTEXT *c, ESYS_TR handle);
+
+void tpm2_pcr_mask_to_selection(uint32_t mask, uint16_t bank, TPML_PCR_SELECTION *ret);
+
+static inline void Esys_Freep(void *p) {
+ if (*(void**) p)
+ sym_Esys_Free(*(void**) p);
+}
+
+#else
+struct tpm2_context;
#endif
+int tpm2_context_init(const char *device, struct tpm2_context *ret);
+void tpm2_context_destroy(struct tpm2_context *c);
+
int tpm2_list_devices(void);
int tpm2_find_device_auto(int log_level, char **ret);
@@ -57,7 +81,8 @@ int tpm2_parse_pcrs(const char *s, uint32_t *ret);
int tpm2_make_pcr_json_array(uint32_t pcr_mask, JsonVariant **ret);
int tpm2_parse_pcr_json_array(JsonVariant *v, uint32_t *ret);
-int tpm2_make_luks2_json(int keyslot, uint32_t pcr_mask, uint16_t pcr_bank, uint16_t primary_alg, const void *blob, size_t blob_size, const void *policy_hash, size_t policy_hash_size, TPM2Flags flags, JsonVariant **ret);
+int tpm2_make_luks2_json(int keyslot, uint32_t hash_pcr_mask, uint16_t pcr_bank, const void *pubkey, size_t pubkey_size, uint32_t pubkey_pcr_mask, uint16_t primary_alg, const void *blob, size_t blob_size, const void *policy_hash, size_t policy_hash_size, TPM2Flags flags, JsonVariant **ret);
+int tpm2_parse_luks2_json(JsonVariant *v, int *ret_keyslot, uint32_t *ret_hash_pcr_mask, uint16_t *ret_pcr_bank, void **ret_pubkey, size_t *ret_pubkey_size, uint32_t *ret_pubkey_pcr_mask, uint16_t *ret_primary_alg, void **ret_blob, size_t *ret_blob_size, void **ret_policy_hash, size_t *ret_policy_hash_size, TPM2Flags *ret_flags);
#define TPM2_PCRS_MAX 24U
@@ -103,6 +128,7 @@ int tpm2_primary_alg_from_string(const char *alg);
typedef struct {
uint32_t search_pcr_mask;
const char *device;
+ const char *signature_path;
} systemd_tpm2_plugin_params;
typedef enum Tpm2Support {
@@ -118,3 +144,8 @@ typedef enum Tpm2Support {
Tpm2Support tpm2_support(void);
int tpm2_parse_pcr_argument(const char *arg, uint32_t *mask);
+
+int tpm2_load_pcr_signature(const char *path, JsonVariant **ret);
+int tpm2_load_pcr_public_key(const char *path, void **ret_pubkey, size_t *ret_pubkey_size);
+
+int pcr_mask_to_string(uint32_t mask, char **ret);
diff --git a/test/TEST-70-TPM2/test.sh b/test/TEST-70-TPM2/test.sh
index 90197a71b9..7c19821ad2 100755
--- a/test/TEST-70-TPM2/test.sh
+++ b/test/TEST-70-TPM2/test.sh
@@ -20,6 +20,7 @@ test_append_files() {
install_dmevent
generate_module_dependencies
inst_binary tpm2_pcrextend
+ inst_binary openssl
}
TEST_70_TPM_DEVICE="tpm-tis"
diff --git a/test/units/testsuite-70.sh b/test/units/testsuite-70.sh
index 1f626ee62f..e2219e89d3 100755
--- a/test/units/testsuite-70.sh
+++ b/test/units/testsuite-70.sh
@@ -57,6 +57,8 @@ env PASSWORD=passphrase systemd-cryptenroll --tpm2-device=auto --tpm2-pcrs=0+7 $
tpm2_pcrextend 0:sha256=0000000000000000000000000000000000000000000000000000000000000000
/usr/lib/systemd/systemd-cryptsetup attach test-volume $img - tpm2-device=auto,headless=1 && exit 1
+rm $img
+
if [[ -e /usr/lib/systemd/systemd-measure ]]; then
echo HALLO > /tmp/tpmdata1
echo foobar > /tmp/tpmdata2
@@ -69,8 +71,65 @@ if [[ -e /usr/lib/systemd/systemd-measure ]]; then
EOF
/usr/lib/systemd/systemd-measure calculate --linux=/tmp/tpmdata1 --initrd=/tmp/tpmdata2 --bank=sha1 --bank=sha256 --bank=sha384 --bank=sha512 | cmp - /tmp/result
+
+ cat >/tmp/result.json < /dev/null && { echo 'unexpected success'; exit 1; }
+
+ # Sign new PCR state, decrypting should work now.
+ /usr/lib/systemd/systemd-measure sign --current --bank=sha1 --bank=sha256 --private-key="/tmp/pcrsign-private.pem" --public-key="/tmp/pcrsign-public.pem" > "/tmp/pcrsign.sig2"
+ systemd-creds decrypt /tmp/pcrtestdata.encrypted - --tpm2-signature="/tmp/pcrsign.sig2" | cmp - /tmp/pcrtestdata
+
+ # Now, do the same, but with a cryptsetup binding
+ truncate -s 20M $img
+ cryptsetup luksFormat -q --pbkdf pbkdf2 --pbkdf-force-iterations 1000 --use-urandom $img /tmp/passphrase
+ systemd-cryptenroll --unlock-key-file=/tmp/passphrase --tpm2-device=auto --tpm2-public-key="/tmp/pcrsign-public.pem" --tpm2-signature="/tmp/pcrsign.sig2" $img
+
+ # Check if we can activate that (without the token module stuff)
+ SYSTEMD_CRYPTSETUP_USE_TOKEN_MODULE=0 /usr/lib/systemd/systemd-cryptsetup attach test-volume2 $img - tpm2-device=auto,tpm2-signature="/tmp/pcrsign.sig2",headless=1
+ SYSTEMD_CRYPTSETUP_USE_TOKEN_MODULE=0 /usr/lib/systemd/systemd-cryptsetup detach test-volume2
+
+ # Check if we can activate that (and a second time with the the token module stuff enabled)
+ SYSTEMD_CRYPTSETUP_USE_TOKEN_MODULE=1 /usr/lib/systemd/systemd-cryptsetup attach test-volume2 $img - tpm2-device=auto,tpm2-signature="/tmp/pcrsign.sig2",headless=1
+ SYSTEMD_CRYPTSETUP_USE_TOKEN_MODULE=1 /usr/lib/systemd/systemd-cryptsetup detach test-volume2
+
+ # After extending the PCR things should fail
+ tpm2_pcrextend 11:sha256=0000000000000000000000000000000000000000000000000000000000000000
+ SYSTEMD_CRYPTSETUP_USE_TOKEN_MODULE=0 /usr/lib/systemd/systemd-cryptsetup attach test-volume2 $img - tpm2-device=auto,tpm2-signature="/tmp/pcrsign.sig2",headless=1 && { echo 'unexpected success'; exit 1; }
+ SYSTEMD_CRYPTSETUP_USE_TOKEN_MODULE=1 /usr/lib/systemd/systemd-cryptsetup attach test-volume2 $img - tpm2-device=auto,tpm2-signature="/tmp/pcrsign.sig2",headless=1 && { echo 'unexpected success'; exit 1; }
+
+ # But once we sign the current PCRs, we should be able to unlock again
+ /usr/lib/systemd/systemd-measure sign --current --bank=sha1 --bank=sha256 --private-key="/tmp/pcrsign-private.pem" --public-key="/tmp/pcrsign-public.pem" > "/tmp/pcrsign.sig3"
+ SYSTEMD_CRYPTSETUP_USE_TOKEN_MODULE=0 /usr/lib/systemd/systemd-cryptsetup attach test-volume2 $img - tpm2-device=auto,tpm2-signature="/tmp/pcrsign.sig3",headless=1
+ /usr/lib/systemd/systemd-cryptsetup detach test-volume2
+ SYSTEMD_CRYPTSETUP_USE_TOKEN_MODULE=1 /usr/lib/systemd/systemd-cryptsetup attach test-volume2 $img - tpm2-device=auto,tpm2-signature="/tmp/pcrsign.sig3",headless=1
+ /usr/lib/systemd/systemd-cryptsetup detach test-volume2
+
+ rm $img
+else
+ echo "/usr/lib/systemd/systemd-measure or PCR sysfs files not found, skipping signed PCR policy test case"
fi
echo OK >/testok