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