From 0d75641388d41e6c493682bc7e6380b981c6ad99 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 12 Aug 2022 15:12:29 +0200 Subject: [PATCH 01/14] tpm2-util: pick up 4 more symbols of tpm2-tss --- src/shared/tpm2-util.c | 10 +++++++++- src/shared/tpm2-util.h | 4 ++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c index fdb305ca48..5e01fc436a 100644 --- a/src/shared/tpm2-util.c +++ b/src/shared/tpm2-util.c @@ -33,15 +33,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 +68,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; diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h index 4030f2a804..fa3ac89da7 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); From bad4c73c37bf4c5ed85e5c41cfdb9a014eb08a17 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 12 Aug 2022 15:36:14 +0200 Subject: [PATCH 02/14] tpm2-util: allow external code to create tpm2 contexts --- src/shared/tpm2-util.c | 37 +++++++++++++------------------------ src/shared/tpm2-util.h | 20 ++++++++++++++++++++ 2 files changed, 33 insertions(+), 24 deletions(-) diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c index 5e01fc436a..5ffddf136f 100644 --- a/src/shared/tpm2-util.c +++ b/src/shared/tpm2-util.c @@ -98,13 +98,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) @@ -125,12 +119,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) @@ -147,7 +136,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; @@ -415,7 +404,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 */ @@ -715,7 +704,7 @@ 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; } @@ -857,7 +846,7 @@ static int tpm2_make_pcr_session( r = 0; finish: - session = flush_context_verbose(c, session); + session = tpm2_flush_context_verbose(c, session); return r; } @@ -919,7 +908,7 @@ int tpm2_seal( start = now(CLOCK_MONOTONIC); - r = tpm2_init(device, &c); + r = tpm2_context_init(device, &c); if (r < 0) return r; @@ -1066,8 +1055,8 @@ 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; } @@ -1133,7 +1122,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; @@ -1230,9 +1219,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; } diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h index fa3ac89da7..cc40027c1b 100644 --- a/src/shared/tpm2-util.h +++ b/src/shared/tpm2-util.h @@ -51,8 +51,28 @@ 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); +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); From e8ccb5c7e1b37b5699d77f1cc6ab1d870d863c5b Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 17 Aug 2022 10:02:02 +0200 Subject: [PATCH 03/14] openssl-util: add helper for calculating fingerprint of a DER public key --- src/shared/openssl-util.c | 58 +++++++++++++++++++++++++++++++++++++++ src/shared/openssl-util.h | 3 ++ 2 files changed, 61 insertions(+) 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..75ef4ba3d4 100644 --- a/src/shared/openssl-util.h +++ b/src/shared/openssl-util.h @@ -39,6 +39,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 From cdaaa62ca15d34b6def1f8217da60cf3ab99e2c9 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 17 Aug 2022 18:40:42 +0200 Subject: [PATCH 04/14] measure: add 'sign' verb --- man/systemd-measure.xml | 118 ++++++++++++--- src/boot/measure.c | 311 ++++++++++++++++++++++++++++++++++++++-- 2 files changed, 398 insertions(+), 31 deletions(-) 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/src/boot/measure.c b/src/boot/measure.c index 33117fe939..3382168869 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; @@ -377,14 +424,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) @@ -419,6 +461,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; @@ -464,6 +525,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; @@ -641,6 +931,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 }, {} }; From d9b5841d40996d42a05b7d6f1adf7a7517966262 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 17 Aug 2022 17:21:57 +0200 Subject: [PATCH 05/14] tpm2-util: extend TPM2 policies to optionally check PCR values against signed values Traditionally, TPM2 PCR policies are bound against literal PCR values, which makes them hard to work with when updating software that is measured into PCRs: each update will change the PCR values, and thus break TPM2 policies of existing objects. Let's improve the situation: let's allow signed PCR policies. Secrets and other TPM2 objects can be associated with a public key that signs a PCR policy. Thus, if the signed policy and the public key is presented, access to the TPM2 object can be granted. This allows a less brittle handling of updates: for example, whenever a kernel image is updated a new signed PCR policy can be shipped along with it, signed by a private key owned by the kernel vendor (ideally: same private key that is used to sign the kernel image itself). TPM2 objects can then be bound to the associated public key, thus allowing objects that can only be unlocked by kernels of the same vendor. This makes it very easy to update kernels without affecting locked secrets. This does not hook up any of the consuming code (just passes NULL/0 everywhere). This is for later commits. --- src/cryptenroll/cryptenroll-tpm2.c | 29 +- src/cryptenroll/cryptenroll-tpm2.h | 2 +- src/cryptsetup/cryptsetup-tokens/luks2-tpm2.c | 9 +- src/cryptsetup/cryptsetup-tpm2.c | 19 +- src/partition/repart.c | 11 +- src/shared/creds-util.c | 15 +- src/shared/openssl-util.h | 8 + src/shared/tpm2-util.c | 480 ++++++++++++++++-- src/shared/tpm2-util.h | 4 +- 9 files changed, 499 insertions(+), 78 deletions(-) diff --git a/src/cryptenroll/cryptenroll-tpm2.c b/src/cryptenroll/cryptenroll-tpm2.c index 01f2e1183c..ab892d3779 100644 --- a/src/cryptenroll/cryptenroll-tpm2.c +++ b/src/cryptenroll/cryptenroll-tpm2.c @@ -130,7 +130,7 @@ 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, bool use_pin) { _cleanup_(erase_and_freep) void *secret = NULL, *secret2 = NULL; @@ -147,7 +147,7 @@ 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_se(node = crypt_get_device_name(cd)); @@ -157,7 +157,16 @@ 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_seal(device, + hash_pcr_mask, + /* pubkey= */ NULL, /* pubkey_size= */ 0, + /* pubkey_pcr_mask= */ 0, + pin_str, + &secret, &secret_size, + &blob, &blob_size, + &hash, &hash_size, + &pcr_bank, + &primary_alg); if (r < 0) return r; @@ -174,7 +183,17 @@ int enroll_tpm2(struct crypt_device *cd, /* 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); + r = tpm2_unseal(device, + hash_pcr_mask, + pcr_bank, + /* pubkey= */ NULL, /* pubkey_size= */ 0, + /* pubkey_pcr_mask= */ 0, + /* signature= */ NULL, + pin_str, + primary_alg, + blob, blob_size, + hash, hash_size, + &secret2, &secret2_size); if (r < 0) return r; @@ -200,7 +219,7 @@ 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, 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..36a3097bc2 100644 --- a/src/cryptenroll/cryptenroll-tpm2.h +++ b/src/cryptenroll/cryptenroll-tpm2.h @@ -7,7 +7,7 @@ #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, 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) { return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), diff --git a/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.c b/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.c index 9f5dd46734..3d633de3f5 100644 --- a/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.c +++ b/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.c @@ -47,10 +47,15 @@ int acquire_luks2_key( return tpm2_unseal( device, - pcr_mask, pcr_bank, + pcr_mask, + pcr_bank, + /* pubkey= */ NULL, /* pubkey_size= */ 0, + /* pubkey_pcr_mask= */ 0, + /* signature_json= */ NULL, + 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); } diff --git a/src/cryptsetup/cryptsetup-tpm2.c b/src/cryptsetup/cryptsetup-tpm2.c index c715c8f232..c348e73b21 100644 --- a/src/cryptsetup/cryptsetup-tpm2.c +++ b/src/cryptsetup/cryptsetup-tpm2.c @@ -55,7 +55,7 @@ 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, uint16_t primary_alg, const char *key_file, @@ -114,14 +114,17 @@ int acquire_tpm2_key( if (!(flags & TPM2_FLAGS_USE_PIN)) return tpm2_unseal( device, - pcr_mask, + hash_pcr_mask, pcr_bank, + /* pubkey= */ NULL, /* pubkey_size= */ 0, + /* pubkey_pcr_mask= */ 0, + /* signature= */ NULL, + /* pin= */ NULL, primary_alg, blob, blob_size, policy_hash, policy_hash_size, - NULL, ret_decrypted_key, ret_decrypted_key_size); @@ -135,16 +138,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= */ NULL, /* pubkey_size= */ 0, + /* pubkey_pcr_mask= */ 0, + /* signature= */ NULL, + 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 diff --git a/src/partition/repart.c b/src/partition/repart.c index 889de712e2..af7a830377 100644 --- a/src/partition/repart.c +++ b/src/partition/repart.c @@ -2919,7 +2919,16 @@ static int partition_encrypt( 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); + r = tpm2_seal(arg_tpm2_device, + arg_tpm2_pcr_mask, + /* pubkey= */ NULL, /* pubkey_size= */ 0, + /* pubkey_pcr_mask= */ 0, + /* 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"); diff --git a/src/shared/creds-util.c b/src/shared/creds-util.c index 03f5fb8c3f..6f061a1aa3 100644 --- a/src/shared/creds-util.c +++ b/src/shared/creds-util.c @@ -518,7 +518,7 @@ 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 void *input, size_t input_size, void **ret, @@ -608,8 +608,10 @@ int encrypt_credential_and_warn( if (try_tpm2) { r = tpm2_seal(tpm2_device, - tpm2_pcr_mask, - NULL, + tpm2_hash_pcr_mask, + /* pubkey= */ NULL, /* pubkey_size= */ 0, + /* pubkey_pcr_mask= */ 0, + /* pin= */ NULL, &tpm2_key, &tpm2_key_size, &tpm2_blob, @@ -716,7 +718,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); @@ -902,12 +904,15 @@ int decrypt_credential_and_warn( r = tpm2_unseal(tpm2_device, le64toh(t->pcr_mask), le16toh(t->pcr_bank), + /* pubkey= */ NULL, /* pubkey_size= */ 0, + /* pubkey_pcr_mask= */ 0, + /* signature= */ NULL, + /* 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) diff --git a/src/shared/openssl-util.h b/src/shared/openssl-util.h index 75ef4ba3d4..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); diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c index 5ffddf136f..1546bb02f9 100644 --- a/src/shared/tpm2-util.c +++ b/src/shared/tpm2-util.c @@ -16,6 +16,7 @@ #include "fs-util.h" #include "hexdecoct.h" #include "memory-util.h" +#include "openssl-util.h" #include "random-util.h" #include "sha256.h" #include "time-util.h" @@ -708,13 +709,197 @@ static int tpm2_make_encryption_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, @@ -726,33 +911,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( @@ -771,23 +967,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, @@ -841,27 +1194,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 = 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; @@ -880,6 +1236,8 @@ int tpm2_seal( TSS2_RC rc; int r; + assert(pubkey || pubkey_size == 0); + assert(ret_secret); assert(ret_secret_size); assert(ret_blob); @@ -888,7 +1246,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 @@ -921,13 +1280,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, @@ -1060,16 +1422,19 @@ finish: 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) { @@ -1089,10 +1454,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) @@ -1166,13 +1533,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, diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h index cc40027c1b..7a61b1e797 100644 --- a/src/shared/tpm2-util.h +++ b/src/shared/tpm2-util.h @@ -48,8 +48,8 @@ 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; From 6a0779cbf9b4d45a64e6beb0fb3892835f4f2905 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 17 Aug 2022 17:28:05 +0200 Subject: [PATCH 06/14] creds-util: hook up new signed PCR policies --- src/core/execute.c | 4 +- src/creds/creds.c | 4 ++ src/shared/creds-util.c | 131 ++++++++++++++++++++++++++++++++-------- src/shared/creds-util.h | 7 ++- src/shared/tpm2-util.c | 46 ++++++++++++++ src/shared/tpm2-util.h | 3 + 6 files changed, 167 insertions(+), 28 deletions(-) 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..03422d31ff 100644 --- a/src/creds/creds.c +++ b/src/creds/creds.c @@ -418,6 +418,7 @@ static int verb_cat(int argc, char **argv, void *userdata) { *cn, timestamp, arg_tpm2_device, + /* tpm2_signature_path= */ NULL, data, size, &plaintext, &plaintext_size); if (r < 0) @@ -490,6 +491,8 @@ static int verb_encrypt(int argc, char **argv, void *userdata) { arg_not_after, arg_tpm2_device, arg_tpm2_pcr_mask, + /* tpm2_pubkey_path= */ NULL, + /* tpm2_pubkey_pcr_mask= */ 0, plaintext, plaintext_size, &output, &output_size); if (r < 0) @@ -577,6 +580,7 @@ static int verb_decrypt(int argc, char **argv, void *userdata) { name, timestamp, arg_tpm2_device, + /* tpm2_signature_path= */ NULL, input, input_size, &plaintext, &plaintext_size); if (r < 0) diff --git a/src/shared/creds-util.c b/src/shared/creds-util.c index 6f061a1aa3..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; @@ -519,6 +529,8 @@ int encrypt_credential_and_warn( 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, @@ -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,20 +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_hash_pcr_mask, - /* pubkey= */ NULL, /* pubkey_size= */ 0, - /* pubkey_pcr_mask= */ 0, + 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_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) { @@ -638,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)) @@ -696,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; @@ -729,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", @@ -800,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; @@ -825,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 @@ -870,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."); @@ -880,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."); @@ -897,16 +958,41 @@ 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), - /* pubkey= */ NULL, /* pubkey_size= */ 0, - /* pubkey_pcr_mask= */ 0, - /* signature= */ NULL, + 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, @@ -918,9 +1004,6 @@ int decrypt_credential_and_warn( 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 @@ -1076,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/tpm2-util.c b/src/shared/tpm2-util.c index 1546bb02f9..aef0603855 100644 --- a/src/shared/tpm2-util.c +++ b/src/shared/tpm2-util.c @@ -9,9 +9,11 @@ #if HAVE_TPM2 #include "alloc-util.h" +#include "def.h" #include "dirent-util.h" #include "dlfcn-util.h" #include "fd-util.h" +#include "fileio.h" #include "format-table.h" #include "fs-util.h" #include "hexdecoct.h" @@ -1955,3 +1957,47 @@ 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; +} diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h index 7a61b1e797..ee6e94ef67 100644 --- a/src/shared/tpm2-util.h +++ b/src/shared/tpm2-util.h @@ -142,3 +142,6 @@ 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); From 75ddec93013fb9bf8f08a2d2cf4374b965a1a583 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 17 Aug 2022 17:28:49 +0200 Subject: [PATCH 07/14] creds-tool: expose new signed PCR policies in creds tool, too --- man/systemd-creds.xml | 37 +++++++++++++++++- src/creds/creds.c | 90 ++++++++++++++++++++++++++++++++----------- 2 files changed, 104 insertions(+), 23 deletions(-) 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/src/creds/creds.c b/src/creds/creds.c index 03422d31ff..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,7 +425,7 @@ static int verb_cat(int argc, char **argv, void *userdata) { *cn, timestamp, arg_tpm2_device, - /* tpm2_signature_path= */ NULL, + arg_tpm2_signature, data, size, &plaintext, &plaintext_size); if (r < 0) @@ -491,8 +498,8 @@ static int verb_encrypt(int argc, char **argv, void *userdata) { arg_not_after, arg_tpm2_device, arg_tpm2_pcr_mask, - /* tpm2_pubkey_path= */ NULL, - /* tpm2_pubkey_pcr_mask= */ 0, + arg_tpm2_public_key, + arg_tpm2_public_key_pcr_mask, plaintext, plaintext_size, &output, &output_size); if (r < 0) @@ -580,7 +587,7 @@ static int verb_decrypt(int argc, char **argv, void *userdata) { name, timestamp, arg_tpm2_device, - /* tpm2_signature_path= */ NULL, + arg_tpm2_signature, input, input_size, &plaintext, &plaintext_size); if (r < 0) @@ -686,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 @@ -711,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' }, {} }; @@ -812,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 @@ -836,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; @@ -885,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; } From f0f4fcaeb77c30df2dc716db15350b5ab49f45e2 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 17 Aug 2022 17:29:44 +0200 Subject: [PATCH 08/14] cryptenroll: hook up new TPM2 signed policies with cryptenroll --- man/systemd-cryptenroll.xml | 53 +++++++++++++++--- src/cryptenroll/cryptenroll-tpm2.c | 87 +++++++++++++++++++++--------- src/cryptenroll/cryptenroll-tpm2.h | 4 +- src/cryptenroll/cryptenroll.c | 74 +++++++++++++++++++------ src/partition/repart.c | 12 ++++- src/shared/tpm2-util.c | 27 +++++++--- src/shared/tpm2-util.h | 2 +- 7 files changed, 201 insertions(+), 58 deletions(-) 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/src/cryptenroll/cryptenroll-tpm2.c b/src/cryptenroll/cryptenroll-tpm2.c index ab892d3779..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" @@ -131,13 +132,16 @@ int enroll_tpm2(struct crypt_device *cd, 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) { - _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; @@ -148,6 +152,7 @@ int enroll_tpm2(struct crypt_device *cd, assert(volume_key); assert(volume_key_size > 0); 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,10 +162,29 @@ int enroll_tpm2(struct crypt_device *cd, return r; } + 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= */ NULL, /* pubkey_size= */ 0, - /* pubkey_pcr_mask= */ 0, + pubkey, pubkey_size, + pubkey_pcr_mask, pin_str, &secret, &secret_size, &blob, &blob_size, @@ -181,24 +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, - hash_pcr_mask, - pcr_bank, - /* pubkey= */ NULL, /* pubkey_size= */ 0, - /* pubkey_pcr_mask= */ 0, - /* signature= */ NULL, - pin_str, - primary_alg, - blob, blob_size, - hash, hash_size, - &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); @@ -219,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, hash_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 36a3097bc2..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 hash_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/partition/repart.c b/src/partition/repart.c index af7a830377..aec04a836c 100644 --- a/src/partition/repart.c +++ b/src/partition/repart.c @@ -2950,7 +2950,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= */ NULL, /* pubkey_size= */ 0, + /* pubkey_pcr_mask= */ 0, + 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"); diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c index aef0603855..254e63334a 100644 --- a/src/shared/tpm2-util.c +++ b/src/shared/tpm2-util.c @@ -1822,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, @@ -1832,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; diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h index ee6e94ef67..3bdeba8f11 100644 --- a/src/shared/tpm2-util.h +++ b/src/shared/tpm2-util.h @@ -81,7 +81,7 @@ 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); #define TPM2_PCRS_MAX 24U From 02ef97cde01ef8e64799befb9583d971f1fe33e6 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 17 Aug 2022 17:30:11 +0200 Subject: [PATCH 09/14] repart: hook up new TPM2 signed policies with repart --- man/systemd-repart.xml | 9 +++++ src/partition/repart.c | 88 ++++++++++++++++++++++++++++++------------ 2 files changed, 73 insertions(+), 24 deletions(-) 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/partition/repart.c b/src/partition/repart.c index aec04a836c..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,15 +2918,27 @@ 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; + 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= */ NULL, /* pubkey_size= */ 0, - /* pubkey_pcr_mask= */ 0, + pubkey, pubkey_size, + arg_tpm2_public_key_pcr_mask, /* pin= */ NULL, &secret, &secret_size, &blob, &blob_size, @@ -2954,8 +2970,8 @@ static int partition_encrypt( keyslot, arg_tpm2_pcr_mask, pcr_bank, - /* pubkey= */ NULL, /* pubkey_size= */ 0, - /* pubkey_pcr_mask= */ 0, + pubkey, pubkey_size, + arg_tpm2_public_key_pcr_mask, primary_alg, blob, blob_size, hash, hash_size, @@ -4461,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" @@ -4495,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 }, {} }; @@ -4709,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; @@ -4762,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; From dc63b2c90940c683a58195f43e59e1c08178629d Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 18 Aug 2022 11:10:30 +0200 Subject: [PATCH 10/14] cryptsetup: hook up signed PCR policies --- man/crypttab.xml | 15 ++++++++ src/cryptsetup/cryptsetup-tpm2.c | 62 ++++++++++++++++++++++++-------- src/cryptsetup/cryptsetup-tpm2.h | 22 +++++++++--- src/cryptsetup/cryptsetup.c | 34 ++++++++++++++---- 4 files changed, 108 insertions(+), 25 deletions(-) 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/src/cryptsetup/cryptsetup-tpm2.c b/src/cryptsetup/cryptsetup-tpm2.c index c348e73b21..7469e7da1b 100644 --- a/src/cryptsetup/cryptsetup-tpm2.c +++ b/src/cryptsetup/cryptsetup-tpm2.c @@ -57,6 +57,10 @@ int acquire_tpm2_key( 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, 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,14 +116,20 @@ 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, hash_pcr_mask, pcr_bank, - /* pubkey= */ NULL, /* pubkey_size= */ 0, - /* pubkey_pcr_mask= */ 0, - /* signature= */ NULL, + pubkey, pubkey_size, + pubkey_pcr_mask, + signature_json, /* pin= */ NULL, primary_alg, blob, @@ -141,9 +152,9 @@ int acquire_tpm2_key( r = tpm2_unseal(device, hash_pcr_mask, pcr_bank, - /* pubkey= */ NULL, /* pubkey_size= */ 0, - /* pubkey_pcr_mask= */ 0, - /* signature= */ NULL, + pubkey, pubkey_size, + pubkey_pcr_mask, + signature_json, pin_str, primary_alg, blob, @@ -167,8 +178,11 @@ 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, @@ -178,11 +192,11 @@ int find_tpm2_auto_data( int *ret_token, TPM2Flags *ret_flags) { - _cleanup_free_ void *blob = NULL, *policy_hash = NULL; - size_t blob_size = 0, policy_hash_size = 0; + _cleanup_free_ void *blob = NULL, *policy_hash = NULL, *pubkey = NULL; + size_t blob_size = 0, policy_hash_size = 0, pubkey_size = 0; int r, keyslot = -1, token = -1; TPM2Flags flags = 0; - uint32_t pcr_mask = 0; + uint32_t hash_pcr_mask = 0, pubkey_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 */ @@ -212,12 +226,12 @@ int find_tpm2_auto_data( return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "TPM2 token data lacks 'tpm2-pcrs' field."); - r = tpm2_parse_pcr_json_array(w, &pcr_mask); + r = tpm2_parse_pcr_json_array(w, &hash_pcr_mask); if (r < 0) return log_error_errno(r, "Failed to parse TPM2 PCR mask: %m"); if (search_pcr_mask != UINT32_MAX && - search_pcr_mask != pcr_mask) /* PCR mask doesn't match what is configured, ignore this entry */ + search_pcr_mask != hash_pcr_mask) /* PCR mask doesn't match what is configured, ignore this entry */ continue; assert(keyslot < 0); @@ -292,6 +306,21 @@ int find_tpm2_auto_data( flags |= TPM2_FLAGS_USE_PIN; } + 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_error_errno(r, "Failed to decode PCR public key."); + } else if (pubkey_pcr_mask != 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Public key PCR mask set, but not public key included in JSON data, refusing."); + break; } @@ -302,15 +331,18 @@ int find_tpm2_auto_data( if (start_token <= 0) log_info("Automatically discovered security TPM2 token unlocks volume."); - *ret_pcr_mask = pcr_mask; + *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_pcr_bank = pcr_bank; - *ret_primary_alg = primary_alg; *ret_flags = flags; return 0; diff --git a/src/cryptsetup/cryptsetup-tpm2.h b/src/cryptsetup/cryptsetup-tpm2.h index ab16d0a18f..b1bcf6de10 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,8 +39,11 @@ 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, @@ -51,8 +58,12 @@ int find_tpm2_auto_data( 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,8 +87,11 @@ 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, diff --git a/src/cryptsetup/cryptsetup.c b/src/cryptsetup/cryptsetup.c index 90bce953de..c419354bf5 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); @@ -1441,10 +1453,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 +1505,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,8 +1515,10 @@ 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, @@ -1523,10 +1542,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, From fdf6c27cbaea5af63b474b6160c1effa5f3a3b46 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 19 Aug 2022 22:15:12 +0200 Subject: [PATCH 11/14] tpm2-util: add common parser for the LUKS2 TPM2 JSON structure This splits out the JSON parser used by the systemd-cryptsetup code. This is preparation for later work to reuse it in the tpm2 cryptsetup token module, which currently uses a separate but very similar parser for the same data. No change in behaviour. --- src/cryptsetup/cryptsetup-tpm2.c | 177 ++++++++----------------------- src/cryptsetup/cryptsetup-tpm2.h | 8 +- src/cryptsetup/cryptsetup.c | 4 +- src/shared/cryptsetup-util.c | 46 ++++---- src/shared/cryptsetup-util.h | 3 +- src/shared/tpm2-util.c | 160 ++++++++++++++++++++++++++-- src/shared/tpm2-util.h | 1 + 7 files changed, 226 insertions(+), 173 deletions(-) diff --git a/src/cryptsetup/cryptsetup-tpm2.c b/src/cryptsetup/cryptsetup-tpm2.c index 7469e7da1b..838c02bfc9 100644 --- a/src/cryptsetup/cryptsetup-tpm2.c +++ b/src/cryptsetup/cryptsetup-tpm2.c @@ -188,24 +188,22 @@ int find_tpm2_auto_data( 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, *pubkey = NULL; - size_t blob_size = 0, policy_hash_size = 0, pubkey_size = 0; - int r, keyslot = -1, token = -1; - TPM2Flags flags = 0; - uint32_t hash_pcr_mask = 0, pubkey_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)) @@ -213,137 +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, &hash_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 != hash_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; - } - - 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_error_errno(r, "Failed to decode PCR public key."); - } else if (pubkey_pcr_mask != 0) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Public key PCR mask set, but not public key included in JSON data, refusing."); - - 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_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; + 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 b1bcf6de10..a34eb8443d 100644 --- a/src/cryptsetup/cryptsetup-tpm2.h +++ b/src/cryptsetup/cryptsetup-tpm2.h @@ -49,9 +49,9 @@ int find_tpm2_auto_data( 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 @@ -97,9 +97,9 @@ static inline int find_tpm2_auto_data( 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 c419354bf5..406369329c 100644 --- a/src/cryptsetup/cryptsetup.c +++ b/src/cryptsetup/cryptsetup.c @@ -1522,9 +1522,9 @@ static int attach_luks_or_plain_or_bitlk_by_tpm2( &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, 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/tpm2-util.c b/src/shared/tpm2-util.c index 254e63334a..f5b97fae73 100644 --- a/src/shared/tpm2-util.c +++ b/src/shared/tpm2-util.c @@ -1,17 +1,12 @@ /* 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" @@ -19,10 +14,15 @@ #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; @@ -1881,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"; diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h index 3bdeba8f11..2ff7c0eb63 100644 --- a/src/shared/tpm2-util.h +++ b/src/shared/tpm2-util.h @@ -82,6 +82,7 @@ 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 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 From 4d5cc0d45322e71cf02cbef3022ff745e4bb3433 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 19 Aug 2022 22:18:31 +0200 Subject: [PATCH 12/14] tpm2-util: add helper for formatting PCR masks as string --- src/shared/tpm2-util.c | 19 +++++++++++++++++++ src/shared/tpm2-util.h | 2 ++ 2 files changed, 21 insertions(+) diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c index f5b97fae73..7c9eb77f95 100644 --- a/src/shared/tpm2-util.c +++ b/src/shared/tpm2-util.c @@ -2160,3 +2160,22 @@ int tpm2_load_pcr_public_key(const char *path, void **ret_pubkey, size_t *ret_pu 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 2ff7c0eb63..554ca58df6 100644 --- a/src/shared/tpm2-util.h +++ b/src/shared/tpm2-util.h @@ -146,3 +146,5 @@ 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); From 75a9681ec07bee537d037843781a9ee539e1b7c3 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 19 Aug 2022 22:18:40 +0200 Subject: [PATCH 13/14] cryptsetup: hook up TPM2 token code with policies based on PCR signatures, too --- .../cryptsetup-token-systemd-tpm2.c | 146 +++++++++++------- src/cryptsetup/cryptsetup-tokens/luks2-tpm2.c | 132 +++------------- src/cryptsetup/cryptsetup-tokens/luks2-tpm2.h | 18 +-- src/cryptsetup/cryptsetup.c | 3 +- src/shared/tpm2-util.h | 1 + 5 files changed, 118 insertions(+), 182 deletions(-) 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 3d633de3f5..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,121 +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, + hash_pcr_mask, pcr_bank, - /* pubkey= */ NULL, /* pubkey_size= */ 0, - /* pubkey_pcr_mask= */ 0, - /* signature_json= */ NULL, + pubkey, pubkey_size, + pubkey_pcr_mask, + signature_json, pin, primary_alg, key_data, key_data_size, 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.c b/src/cryptsetup/cryptsetup.c index 406369329c..d6e5833b24 100644 --- a/src/cryptsetup/cryptsetup.c +++ b/src/cryptsetup/cryptsetup.c @@ -1396,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()) diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h index 554ca58df6..cd6bcc91b3 100644 --- a/src/shared/tpm2-util.h +++ b/src/shared/tpm2-util.h @@ -128,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 { From 7c46b19718b990e9fa188faccb6f8b69529d5936 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 19 Aug 2022 11:26:49 +0200 Subject: [PATCH 14/14] TEST-70-TPM2: add test for new signed TPM2 PCR policies --- test/TEST-70-TPM2/test.sh | 1 + test/units/testsuite-70.sh | 61 +++++++++++++++++++++++++++++++++++++- 2 files changed, 61 insertions(+), 1 deletion(-) 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