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

pcrlock: add new pcrlock tool

This commit is contained in:
Lennart Poettering 2023-08-16 11:39:23 +02:00
parent b52e950598
commit a434270139
15 changed files with 6159 additions and 5 deletions

View File

@ -2174,6 +2174,7 @@ subdir('src/oom')
subdir('src/partition') subdir('src/partition')
subdir('src/path') subdir('src/path')
subdir('src/pcrextend') subdir('src/pcrextend')
subdir('src/pcrlock')
subdir('src/portable') subdir('src/portable')
subdir('src/pstore') subdir('src/pstore')
subdir('src/quotacheck') subdir('src/quotacheck')

View File

@ -248,6 +248,7 @@ int enroll_tpm2(struct crypt_device *cd,
n_hash_pcr_values, n_hash_pcr_values,
pubkey ? &public : NULL, pubkey ? &public : NULL,
use_pin, use_pin,
/* pcrlock_policy= */ NULL,
&policy); &policy);
if (r < 0) if (r < 0)
return r; return r;
@ -288,6 +289,7 @@ int enroll_tpm2(struct crypt_device *cd,
pubkey_pcr_mask, pubkey_pcr_mask,
signature_json, signature_json,
pin_str, pin_str,
/* pcrlock_policy= */ NULL,
/* primary_alg= */ 0, /* primary_alg= */ 0,
blob, blob_size, blob, blob_size,
policy.buffer, policy.size, policy.buffer, policy.size,

View File

@ -88,6 +88,7 @@ int acquire_luks2_key(
pubkey_pcr_mask, pubkey_pcr_mask,
signature_json, signature_json,
pin, pin,
/* pcrlock_policy= */ NULL,
primary_alg, primary_alg,
key_data, key_data_size, key_data, key_data_size,
policy_hash, policy_hash_size, policy_hash, policy_hash_size,

View File

@ -142,6 +142,7 @@ int acquire_tpm2_key(
pubkey_pcr_mask, pubkey_pcr_mask,
signature_json, signature_json,
/* pin= */ NULL, /* pin= */ NULL,
/* pcrlock_policy= */ NULL,
primary_alg, primary_alg,
blob, blob,
blob_size, blob_size,
@ -189,6 +190,7 @@ int acquire_tpm2_key(
pubkey_pcr_mask, pubkey_pcr_mask,
signature_json, signature_json,
b64_salted_pin, b64_salted_pin,
/* pcrlock_policy= */ NULL,
primary_alg, primary_alg,
blob, blob,
blob_size, blob_size,

View File

@ -3822,7 +3822,13 @@ static int partition_encrypt(Context *context, Partition *p, PartitionTarget *ta
} }
TPM2B_DIGEST policy = TPM2B_DIGEST_MAKE(NULL, TPM2_SHA256_DIGEST_SIZE); TPM2B_DIGEST policy = TPM2B_DIGEST_MAKE(NULL, TPM2_SHA256_DIGEST_SIZE);
r = tpm2_calculate_sealing_policy(arg_tpm2_hash_pcr_values, arg_tpm2_n_hash_pcr_values, pubkey ? &public : NULL, /* use_pin= */ false, &policy); r = tpm2_calculate_sealing_policy(
arg_tpm2_hash_pcr_values,
arg_tpm2_n_hash_pcr_values,
pubkey ? &public : NULL,
/* use_pin= */ false,
/* pcrlock_policy= */ NULL,
&policy);
if (r < 0) if (r < 0)
return log_error_errno(r, "Could not calculate sealing policy digest: %m"); return log_error_errno(r, "Could not calculate sealing policy digest: %m");

21
src/pcrlock/meson.build Normal file
View File

@ -0,0 +1,21 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
executables += [
libexec_template + {
'name' : 'systemd-pcrlock',
'conditions' : [
'HAVE_OPENSSL',
'HAVE_TPM2'
],
'sources' : files(
'pcrlock.c',
'pcrlock-firmware.c',
'pehash.c',
),
'dependencies' : [
libm,
libopenssl,
tpm2,
],
},
]

View File

@ -0,0 +1,168 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <openssl/evp.h>
#include "pcrlock-firmware.h"
#include "unaligned.h"
static int tcg_pcr_event2_digests_size(
const TCG_EfiSpecIdEventAlgorithmSize *algorithms,
size_t n_algorithms,
size_t *ret) {
size_t m = 0;
assert(algorithms || n_algorithms == 0);
assert(ret);
FOREACH_ARRAY(a, algorithms, n_algorithms) {
if (a->digestSize > UINT32_MAX - offsetof(TPMT_HA, digest) - m)
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Accumulated hash size too large");
m += offsetof(TPMT_HA, digest) + a->digestSize;
}
*ret = m;
return 0;
}
int validate_firmware_event(
const TCG_PCR_EVENT2 *event,
size_t left,
const TCG_EfiSpecIdEventAlgorithmSize *algorithms,
size_t n_algorithms,
const TCG_PCR_EVENT2 **ret_next_event,
size_t *ret_left,
const void **ret_payload,
size_t *ret_payload_size) {
size_t digests_size;
int r;
assert(event);
assert(algorithms || n_algorithms == 0);
assert(ret_next_event);
assert(ret_left);
if (left == 0) {
*ret_next_event = NULL;
*ret_left = 0;
return 0;
}
r = tcg_pcr_event2_digests_size(algorithms, n_algorithms, &digests_size);
if (r < 0)
return r;
if (left < (uint64_t) offsetof(TCG_PCR_EVENT2, digests.digests) + (uint64_t) digests_size + sizeof(uint32_t))
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Event header too short.");
if (event->digests.count != n_algorithms)
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Number of digests in event doesn't match log.");
uint32_t eventSize = unaligned_read_ne32((const uint8_t*) &event->digests.digests + digests_size);
uint64_t size = (uint64_t) offsetof(TCG_PCR_EVENT2, digests.digests) + (uint64_t) digests_size + sizeof(uint32_t) + eventSize;
if (size > left)
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Event header too short.");
*ret_next_event = (const TCG_PCR_EVENT2*) ((const uint8_t*) event + size);
*ret_left = left - size;
if (ret_payload)
*ret_payload = (const uint8_t*) &event->digests.digests + digests_size + sizeof(uint32_t);
if (ret_payload_size)
*ret_payload_size = eventSize;
return 1;
}
int validate_firmware_header(
const void *start,
size_t size,
const TCG_EfiSpecIdEventAlgorithmSize **ret_algorithms,
size_t *ret_n_algorithms,
const TCG_PCR_EVENT2 **ret_first,
size_t *ret_left) {
assert(start || size == 0);
assert(ret_algorithms);
assert(ret_n_algorithms);
assert(ret_first);
assert(ret_left);
if (size < offsetof(TCG_PCClientPCREvent, event))
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Event log too short for TCG_PCClientPCREvent.");
const TCG_PCClientPCREvent *h = start;
if (size < (uint64_t) offsetof(TCG_PCClientPCREvent, event) + (uint64_t) h->eventDataSize)
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Event log too short for TCG_PCClientPCREvent events data.");
if (h->pcrIndex != 0)
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Event log header has unexpected PCR index %" PRIu32, h->pcrIndex);
if (h->eventType != EV_NO_ACTION)
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Event log header has unexpected event type 0x%" PRIx32, h->eventType);
if (!memeqzero(h->digest, sizeof(h->digest)))
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Event log header has unexpected non-zero digest.");
if (h->eventDataSize < offsetof(TCG_EfiSpecIDEvent, digestSizes))
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Event log header too short for TCG_EfiSpecIdEvent.");
const TCG_EfiSpecIDEvent *id = (const TCG_EfiSpecIDEvent*) h->event;
/* Signature as per "TCG PC Client Specific Platform Firmware Profile Specification"
* (https://trustedcomputinggroup.org/resource/pc-client-specific-platform-firmware-profile-specification/),
* section 10.4.5.1 "Specification ID Version Event" (at least in version 1.05 Revision 23 of the
* spec) */
if (memcmp(id->signature,
(const uint8_t[]) { 0x53, 0x70, 0x65, 0x63, 0x20, 0x49, 0x44, 0x20, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x30, 0x33, 0x00 },
sizeof(id->signature)) != 0)
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Missing TPM2 event log signature.");
if (id->numberOfAlgorithms <= 0)
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Number of advertised hash algorithms is zero.");
if (id->numberOfAlgorithms > UINT32_MAX / sizeof(TCG_EfiSpecIdEventAlgorithmSize))
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Number of advertised hash algorithms too large.");
log_debug("TPM PC Client Platform Firmware Profile: family %u.%u, revision %u.%u",
id->specVersionMajor, id->specVersionMinor,
id->specErrata / 100, id->specErrata % 100);
if (h->eventDataSize < (uint64_t) offsetof(TCG_EfiSpecIDEvent, digestSizes) + (uint64_t) (id->numberOfAlgorithms * sizeof(TCG_EfiSpecIdEventAlgorithmSize)) + 1U)
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Event log header doesn't fit all algorithms.");
uint8_t vendorInfoSize = *((const uint8_t*) id + offsetof(TCG_EfiSpecIDEvent, digestSizes) + (id->numberOfAlgorithms * sizeof(TCG_EfiSpecIdEventAlgorithmSize)));
if (h->eventDataSize != offsetof(TCG_EfiSpecIDEvent, digestSizes) + (id->numberOfAlgorithms * sizeof(TCG_EfiSpecIdEventAlgorithmSize)) + 1U + vendorInfoSize)
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Event log header doesn't fit vendor info.");
for (size_t i = 0; i < id->numberOfAlgorithms; i++) {
const EVP_MD *implementation;
const char *a;
a = tpm2_hash_alg_to_string(id->digestSizes[i].algorithmId);
if (!a) {
log_notice("Event log advertises unknown hash algorithm 0x%4x, can't validate.", id->digestSizes[i].algorithmId);
continue;
}
implementation = EVP_get_digestbyname(a);
if (!implementation) {
log_notice("Event log advertises hash algorithm '%s' we don't implement, can't validate.", a);
continue;
}
if (EVP_MD_size(implementation) != id->digestSizes[i].digestSize)
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Advertised digest size for '%s' is wrong, refusing.", a);
}
*ret_algorithms = id->digestSizes;
*ret_n_algorithms = id->numberOfAlgorithms;
size_t offset = offsetof(TCG_PCClientPCREvent, event) + h->eventDataSize;
*ret_first = (TCG_PCR_EVENT2*) ((const uint8_t*) h + offset);
*ret_left = size - offset;
return 0;
}

View File

@ -0,0 +1,25 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include <sys/types.h>
#include "tpm2-event-log.h"
#include "tpm2-util.h"
int validate_firmware_event(
const TCG_PCR_EVENT2 *event,
size_t left,
const TCG_EfiSpecIdEventAlgorithmSize *algorithms,
size_t n_algorithms,
const TCG_PCR_EVENT2 **ret_next_event,
size_t *ret_left,
const void **ret_payload,
size_t *ret_payload_size);
int validate_firmware_header(
const void *start,
size_t size,
const TCG_EfiSpecIdEventAlgorithmSize **ret_algorithms,
size_t *ret_n_algorithms,
const TCG_PCR_EVENT2 **ret_first,
size_t *ret_left);

4992
src/pcrlock/pcrlock.c Normal file

File diff suppressed because it is too large Load Diff

246
src/pcrlock/pehash.c Normal file
View File

@ -0,0 +1,246 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <sys/stat.h>
#include <unistd.h>
#include "alloc-util.h"
#include "hexdecoct.h"
#include "pe-binary.h"
#include "pehash.h"
#include "sort-util.h"
#include "stat-util.h"
#include "string-table.h"
/* Implements:
*
* https://download.microsoft.com/download/9/c/5/9c5b2167-8017-4bae-9fde-d599bac8184a/authenticode_pe.docx
* Section "Calculating the PE Image Hash"
*/
#define IMAGE_DATA_DIRECTORY_INDEX_CERTIFICATION_TABLE 4U
static int hash_file(int fd, EVP_MD_CTX *md_ctx, uint64_t offset, uint64_t size) {
uint8_t buffer[64*1024];
log_debug("Hashing %" PRIu64 " @ %" PRIu64 " → %" PRIu64, size, offset, offset + size);
while (size > 0) {
size_t m = MIN(size, sizeof(buffer));
ssize_t n;
n = pread(fd, buffer, m, offset);
if (n < 0)
return log_debug_errno(errno, "Failed to read file for hashing: %m");
if ((size_t) n != m)
return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "Short read while hashing.");
if (EVP_DigestUpdate(md_ctx, buffer, m) != 1)
return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Unable to hash data.");
offset += m;
size -= m;
}
return 0;
}
static int section_offset_cmp(const IMAGE_SECTION_HEADER *a, const IMAGE_SECTION_HEADER *b) {
return CMP(ASSERT_PTR(a)->PointerToRawData, ASSERT_PTR(b)->PointerToRawData);
}
int pe_hash(int fd,
const EVP_MD *md,
void **ret_hash,
size_t *ret_hash_size) {
_cleanup_(EVP_MD_CTX_freep) EVP_MD_CTX *mdctx = NULL;
_cleanup_free_ IMAGE_SECTION_HEADER *sections = NULL;
_cleanup_free_ IMAGE_DOS_HEADER *dos_header = NULL;
_cleanup_free_ PeHeader *pe_header = NULL;
const IMAGE_DATA_DIRECTORY *certificate_table;
struct stat st;
uint64_t p, q;
int r;
assert(fd >= 0);
assert(md);
assert(ret_hash_size);
assert(ret_hash);
if (fstat(fd, &st) < 0)
return log_debug_errno(errno, "Failed to stat file: %m");
r = stat_verify_regular(&st);
if (r < 0)
return log_debug_errno(r, "Not a regular file: %m");
r = pe_load_headers(fd, &dos_header, &pe_header);
if (r < 0)
return r;
r = pe_load_sections(fd, dos_header, pe_header, &sections);
if (r < 0)
return r;
certificate_table = pe_header_get_data_directory(pe_header, IMAGE_DATA_DIRECTORY_INDEX_CERTIFICATION_TABLE);
if (!certificate_table)
return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "File lacks certificate table.");
mdctx = EVP_MD_CTX_new();
if (!mdctx)
return log_oom_debug();
if (EVP_DigestInit_ex(mdctx, md, NULL) != 1)
return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to allocate message digest.");
/* Everything from beginning of file to CheckSum field in PE header */
p = (uint64_t) dos_header->e_lfanew +
offsetof(PeHeader, optional.CheckSum);
r = hash_file(fd, mdctx, 0, p);
if (r < 0)
return r;
p += sizeof(le32_t);
/* Everything between the CheckSum field and the Image Data Directory Entry for the Certification Table */
q = (uint64_t) dos_header->e_lfanew +
PE_HEADER_OPTIONAL_FIELD_OFFSET(pe_header, DataDirectory[IMAGE_DATA_DIRECTORY_INDEX_CERTIFICATION_TABLE]);
r = hash_file(fd, mdctx, p, q - p);
if (r < 0)
return r;
q += sizeof(IMAGE_DATA_DIRECTORY);
/* The rest of the header + the section table */
p = pe_header->optional.SizeOfHeaders;
if (p < q)
return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "SizeOfHeaders too short.");
r = hash_file(fd, mdctx, q, p - q);
if (r < 0)
return r;
/* Sort by location in file */
typesafe_qsort(sections, pe_header->pe.NumberOfSections, section_offset_cmp);
FOREACH_ARRAY(section, sections, pe_header->pe.NumberOfSections) {
r = hash_file(fd, mdctx, section->PointerToRawData, section->SizeOfRawData);
if (r < 0)
return r;
p += section->SizeOfRawData;
}
if ((uint64_t) st.st_size > p) {
if (st.st_size - p < certificate_table->Size)
return log_debug_errno(errno, "No space for certificate table, refusing.");
r = hash_file(fd, mdctx, p, st.st_size - p - certificate_table->Size);
if (r < 0)
return r;
}
int hsz = EVP_MD_CTX_size(mdctx);
if (hsz < 0)
return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to get hash size.");
unsigned hash_size = (unsigned) hsz;
_cleanup_free_ void *hash = malloc(hsz);
if (!hash)
return log_oom_debug();
if (EVP_DigestFinal_ex(mdctx, hash, &hash_size) != 1)
return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to finalize hash function.");
assert(hash_size == (unsigned) hsz);
*ret_hash = TAKE_PTR(hash);
*ret_hash_size = hash_size;
return 0;
}
typedef void* SectionHashArray[_UNIFIED_SECTION_MAX];
static void section_hash_array_done(SectionHashArray *array) {
assert(array);
for (size_t i = 0; i < _UNIFIED_SECTION_MAX; i++)
free((*array)[i]);
}
int uki_hash(int fd,
const EVP_MD *md,
void* ret_hashes[static _UNIFIED_SECTION_MAX],
size_t *ret_hash_size) {
_cleanup_(section_hash_array_done) SectionHashArray hashes = {};
_cleanup_free_ IMAGE_SECTION_HEADER *sections = NULL;
_cleanup_free_ IMAGE_DOS_HEADER *dos_header = NULL;
_cleanup_free_ PeHeader *pe_header = NULL;
int r;
assert(fd >= 0);
assert(ret_hashes);
assert(ret_hash_size);
r = pe_load_headers(fd, &dos_header, &pe_header);
if (r < 0)
return r;
r = pe_load_sections(fd, dos_header, pe_header, &sections);
if (r < 0)
return r;
int hsz = EVP_MD_size(md);
if (hsz < 0)
return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to get hash size.");
FOREACH_ARRAY(section, sections, pe_header->pe.NumberOfSections) {
_cleanup_(EVP_MD_CTX_freep) EVP_MD_CTX *mdctx = NULL;
_cleanup_free_ char *n = NULL;
ssize_t i;
n = memdup_suffix0(section->Name, sizeof(section->Name));
if (!n)
return log_oom_debug();
i = string_table_lookup(unified_sections, _UNIFIED_SECTION_MAX, n);
if (i < 0)
continue;
if (hashes[i])
return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "Duplicate section");
mdctx = EVP_MD_CTX_new();
if (!mdctx)
return log_oom_debug();
if (EVP_DigestInit_ex(mdctx, md, NULL) != 1)
return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to allocate message digest.");
r = hash_file(fd, mdctx, section->PointerToRawData, section->VirtualSize);
if (r < 0)
return r;
hashes[i] = malloc(hsz);
if (!hashes[i])
return log_oom_debug();
unsigned hash_size = (unsigned) hsz;
if (EVP_DigestFinal_ex(mdctx, hashes[i], &hash_size) != 1)
return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to finalize hash function.");
assert(hash_size == (unsigned) hsz);
if (DEBUG_LOGGING) {
_cleanup_free_ char *hs = NULL;
hs = hexmem(hashes[i], hsz);
log_debug("Section %s with %s is %s.", n, EVP_MD_name(md), strna(hs));
}
}
memcpy(ret_hashes, hashes, sizeof(hashes));
zero(hashes);
*ret_hash_size = (unsigned) hsz;
return 0;
}

11
src/pcrlock/pehash.h Normal file
View File

@ -0,0 +1,11 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include <sys/types.h>
#include "openssl-util.h"
#include "uki.h"
int pe_hash(int fd, const EVP_MD *md, void **ret_hash, size_t *ret_hash_size);
int uki_hash(int fd, const EVP_MD *md, void *ret_hashes[static _UNIFIED_SECTION_MAX], size_t *ret_hash_size);

View File

@ -856,6 +856,7 @@ int encrypt_credential_and_warn(
tpm2_n_hash_pcr_values, tpm2_n_hash_pcr_values,
pubkey ? &public : NULL, pubkey ? &public : NULL,
/* use_pin= */ false, /* use_pin= */ false,
/* pcrlock_policy= */ NULL,
&tpm2_policy); &tpm2_policy);
if (r < 0) if (r < 0)
return log_error_errno(r, "Could not calculate sealing policy digest: %m"); return log_error_errno(r, "Could not calculate sealing policy digest: %m");
@ -1219,6 +1220,7 @@ int decrypt_credential_and_warn(
z ? le64toh(z->pcr_mask) : 0, z ? le64toh(z->pcr_mask) : 0,
signature_json, signature_json,
/* pin= */ NULL, /* pin= */ NULL,
/* pcrlock_policy= */ NULL,
le16toh(t->primary_alg), le16toh(t->primary_alg),
t->policy_hash_and_blob, t->policy_hash_and_blob,
le32toh(t->blob_size), le32toh(t->blob_size),

View File

@ -3906,6 +3906,7 @@ int tpm2_calculate_sealing_policy(
size_t n_pcr_values, size_t n_pcr_values,
const TPM2B_PUBLIC *public, const TPM2B_PUBLIC *public,
bool use_pin, bool use_pin,
const Tpm2PCRLockPolicy *pcrlock_policy,
TPM2B_DIGEST *digest) { TPM2B_DIGEST *digest) {
int r; int r;
@ -3913,12 +3914,30 @@ int tpm2_calculate_sealing_policy(
assert(pcr_values || n_pcr_values == 0); assert(pcr_values || n_pcr_values == 0);
assert(digest); assert(digest);
if (public && pcrlock_policy)
return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Policies with both signed PCR and pcrlock are currently not supported.");
if (public) { if (public) {
r = tpm2_calculate_policy_authorize(public, NULL, digest); r = tpm2_calculate_policy_authorize(public, NULL, digest);
if (r < 0) if (r < 0)
return r; return r;
} }
if (pcrlock_policy) {
TPM2B_NV_PUBLIC nv_public;
r = tpm2_unmarshal_nv_public(
pcrlock_policy->nv_public.iov_base,
pcrlock_policy->nv_public.iov_len,
&nv_public);
if (r < 0)
return r;
r = tpm2_calculate_policy_authorize_nv(&nv_public, digest);
if (r < 0)
return r;
}
if (n_pcr_values > 0) { if (n_pcr_values > 0) {
r = tpm2_calculate_policy_pcr(pcr_values, n_pcr_values, digest); r = tpm2_calculate_policy_pcr(pcr_values, n_pcr_values, digest);
if (r < 0) if (r < 0)
@ -3945,6 +3964,7 @@ static int tpm2_build_sealing_policy(
uint32_t pubkey_pcr_mask, uint32_t pubkey_pcr_mask,
JsonVariant *signature_json, JsonVariant *signature_json,
bool use_pin, bool use_pin,
const Tpm2PCRLockPolicy *pcrlock_policy,
TPM2B_DIGEST **ret_policy_digest) { TPM2B_DIGEST **ret_policy_digest) {
int r; int r;
@ -3963,6 +3983,9 @@ static int tpm2_build_sealing_policy(
log_debug("Selected TPM2 PCRs are not initialized on this system."); log_debug("Selected TPM2 PCRs are not initialized on this system.");
} }
if (pubkey_pcr_mask != 0 && pcrlock_policy)
return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Policies with both signed PCR and pcrlock are currently not supported.");
if (pubkey_pcr_mask != 0) { if (pubkey_pcr_mask != 0) {
TPML_PCR_SELECTION pcr_selection; TPML_PCR_SELECTION pcr_selection;
tpm2_tpml_pcr_selection_from_mask(pubkey_pcr_mask, (TPMI_ALG_HASH)pcr_bank, &pcr_selection); tpm2_tpml_pcr_selection_from_mask(pubkey_pcr_mask, (TPMI_ALG_HASH)pcr_bank, &pcr_selection);
@ -3971,6 +3994,34 @@ static int tpm2_build_sealing_policy(
return r; return r;
} }
if (pcrlock_policy) {
_cleanup_(tpm2_handle_freep) Tpm2Handle *nv_handle = NULL;
r = tpm2_policy_super_pcr(
c,
session,
&pcrlock_policy->prediction,
pcrlock_policy->algorithm);
if (r < 0)
return r;
r = tpm2_deserialize(
c,
pcrlock_policy->nv_handle.iov_base,
pcrlock_policy->nv_handle.iov_len,
&nv_handle);
if (r < 0)
return r;
r = tpm2_policy_authorize_nv(
c,
session,
nv_handle,
NULL);
if (r < 0)
return r;
}
if (hash_pcr_mask != 0) { if (hash_pcr_mask != 0) {
TPML_PCR_SELECTION pcr_selection; TPML_PCR_SELECTION pcr_selection;
tpm2_tpml_pcr_selection_from_mask(hash_pcr_mask, (TPMI_ALG_HASH)pcr_bank, &pcr_selection); tpm2_tpml_pcr_selection_from_mask(hash_pcr_mask, (TPMI_ALG_HASH)pcr_bank, &pcr_selection);
@ -4558,6 +4609,7 @@ int tpm2_unseal(Tpm2Context *c,
uint32_t pubkey_pcr_mask, uint32_t pubkey_pcr_mask,
JsonVariant *signature, JsonVariant *signature,
const char *pin, const char *pin,
const Tpm2PCRLockPolicy *pcrlock_policy,
uint16_t primary_alg, uint16_t primary_alg,
const void *blob, const void *blob,
size_t blob_size, size_t blob_size,
@ -4695,6 +4747,7 @@ int tpm2_unseal(Tpm2Context *c,
pubkey_pcr_mask, pubkey_pcr_mask,
signature, signature,
!!pin, !!pin,
pcrlock_policy,
&policy_digest); &policy_digest);
if (r < 0) if (r < 0)
return r; return r;
@ -5417,7 +5470,6 @@ int tpm2_extend_bytes(
return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "OpenSSL support is disabled."); return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "OpenSSL support is disabled.");
#endif #endif
} }
#endif
const uint16_t tpm2_hash_algorithms[] = { const uint16_t tpm2_hash_algorithms[] = {
TPM2_ALG_SHA1, TPM2_ALG_SHA1,
@ -5429,6 +5481,565 @@ const uint16_t tpm2_hash_algorithms[] = {
assert_cc(ELEMENTSOF(tpm2_hash_algorithms) == TPM2_N_HASH_ALGORITHMS + 1); assert_cc(ELEMENTSOF(tpm2_hash_algorithms) == TPM2_N_HASH_ALGORITHMS + 1);
static size_t tpm2_hash_algorithm_index(uint16_t algorithm) {
for (size_t i = 0; i < TPM2_N_HASH_ALGORITHMS; i++)
if (tpm2_hash_algorithms[i] == algorithm)
return i;
return SIZE_MAX;
}
TPM2B_DIGEST *tpm2_pcr_prediction_result_get_hash(Tpm2PCRPredictionResult *result, uint16_t alg) {
size_t alg_idx;
assert(result);
alg_idx = tpm2_hash_algorithm_index(alg);
if (alg_idx == SIZE_MAX) /* Algorithm not known? */
return NULL;
if (result->hash[alg_idx].size <= 0) /* No hash value for this algorithm? */
return NULL;
return result->hash + alg_idx;
}
void tpm2_pcr_prediction_done(Tpm2PCRPrediction *p) {
assert(p);
for (uint32_t pcr = 0; pcr < TPM2_PCRS_MAX; pcr++)
ordered_set_free(p->results[pcr]);
}
static void tpm2_pcr_prediction_result_hash_func(const Tpm2PCRPredictionResult *banks, struct siphash *state) {
assert(banks);
for (size_t i = 0; i < TPM2_N_HASH_ALGORITHMS; i++)
siphash24_compress_safe(banks->hash[i].buffer, banks->hash[i].size, state);
}
static int tpm2_pcr_prediction_result_compare_func(const Tpm2PCRPredictionResult *a, const Tpm2PCRPredictionResult *b) {
int r;
assert(a);
assert(b);
for (size_t i = 0; i < TPM2_N_HASH_ALGORITHMS; i++) {
r = memcmp_nn(a->hash[i].buffer, a->hash[i].size,
b->hash[i].buffer, b->hash[i].size);
if (r != 0)
return r;
}
return 0;
}
DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR(
tpm2_pcr_prediction_result_hash_ops,
Tpm2PCRPredictionResult,
tpm2_pcr_prediction_result_hash_func,
tpm2_pcr_prediction_result_compare_func,
Tpm2PCRPredictionResult,
free);
static Tpm2PCRPredictionResult *find_prediction_result_by_algorithm(OrderedSet *set, Tpm2PCRPredictionResult *result, size_t alg_idx) {
Tpm2PCRPredictionResult *f;
assert(result);
assert(alg_idx != SIZE_MAX);
f = ordered_set_get(set, result); /* Full match? */
if (f)
return f;
/* If this doesn't match full, then see if there an entry that at least matches by the relevant
* algorithm (we are fine if predictions are "incomplete" in some algorithms) */
ORDERED_SET_FOREACH(f, set)
if (memcmp_nn(result->hash[alg_idx].buffer, result->hash[alg_idx].size,
f->hash[alg_idx].buffer, f->hash[alg_idx].size) == 0)
return f;
return NULL;
}
bool tpm2_pcr_prediction_equal(
Tpm2PCRPrediction *a,
Tpm2PCRPrediction *b,
uint16_t algorithm) {
if (a == b)
return true;
if (!a || !b)
return false;
if (a->pcrs != b->pcrs)
return false;
size_t alg_idx = tpm2_hash_algorithm_index(algorithm);
if (alg_idx == SIZE_MAX)
return false;
for (uint32_t pcr = 0; pcr < TPM2_PCRS_MAX; pcr++) {
Tpm2PCRPredictionResult *banks;
ORDERED_SET_FOREACH(banks, a->results[pcr])
if (!find_prediction_result_by_algorithm(b->results[pcr], banks, alg_idx))
return false;
ORDERED_SET_FOREACH(banks, b->results[pcr])
if (!find_prediction_result_by_algorithm(a->results[pcr], banks, alg_idx))
return false;
}
return true;
}
int tpm2_pcr_prediction_to_json(
const Tpm2PCRPrediction *prediction,
uint16_t algorithm,
JsonVariant **ret) {
_cleanup_(json_variant_unrefp) JsonVariant *aj = NULL;
int r;
assert(prediction);
assert(ret);
for (uint32_t pcr = 0; pcr < TPM2_PCRS_MAX; pcr++) {
_cleanup_(json_variant_unrefp) JsonVariant *vj = NULL;
Tpm2PCRPredictionResult *banks;
if (!FLAGS_SET(prediction->pcrs, UINT32_C(1) << pcr))
continue;
ORDERED_SET_FOREACH(banks, prediction->results[pcr]) {
TPM2B_DIGEST *hash = tpm2_pcr_prediction_result_get_hash(banks, algorithm);
if (!hash)
continue;
r = json_variant_append_arrayb(
&vj,
JSON_BUILD_HEX(hash->buffer, hash->size));
if (r < 0)
return log_error_errno(r, "Failed to append hash variant to JSON array: %m");
}
if (!vj)
continue;
r = json_variant_append_arrayb(
&aj,
JSON_BUILD_OBJECT(
JSON_BUILD_PAIR_INTEGER("pcr", pcr),
JSON_BUILD_PAIR_VARIANT("values", vj)));
if (r < 0)
return log_error_errno(r, "Failed to append PCR variants to JSON array: %m");
}
if (!aj) {
r = json_variant_new_array(&aj, NULL, 0);
if (r < 0)
return r;
}
*ret = TAKE_PTR(aj);
return 0;
}
int tpm2_pcr_prediction_from_json(
Tpm2PCRPrediction *prediction,
uint16_t algorithm,
JsonVariant *aj) {
int r;
assert(prediction);
size_t alg_index = tpm2_hash_algorithm_index(algorithm);
assert(alg_index < TPM2_N_HASH_ALGORITHMS);
if (!json_variant_is_array(aj))
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "PCR variant array is not an array.");
JsonVariant *pcr;
JSON_VARIANT_ARRAY_FOREACH(pcr, aj) {
JsonVariant *nr, *values;
nr = json_variant_by_key(pcr, "pcr");
if (!nr)
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "PCR array entry lacks PCR index field");
if (!json_variant_is_unsigned(nr) ||
json_variant_unsigned(nr) >= TPM2_PCRS_MAX)
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "PCR array entry PCR index is not an integer in the range 0…23");
values = json_variant_by_key(pcr, "values");
if (!values)
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "PCR array entry lacks values field");
if (!json_variant_is_array(values))
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "PCR array entry values field is not an array");
prediction->pcrs |= UINT32_C(1) << json_variant_unsigned(nr);
JsonVariant *v;
JSON_VARIANT_ARRAY_FOREACH(v, values) {
_cleanup_free_ void *buffer = NULL;
size_t size;
r = json_variant_unhex(v, &buffer, &size);
if (r < 0)
return log_error_errno(r, "Failed to decode PCR policy array hash value");
if (size <= 0)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "PCR policy array hash value is zero.");
if (size > sizeof_field(TPM2B_DIGEST, buffer))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "PCR policy array hash value is too large.");
_cleanup_free_ Tpm2PCRPredictionResult *banks = new0(Tpm2PCRPredictionResult, 1);
if (!banks)
return log_oom();
memcpy(banks->hash[alg_index].buffer, buffer, size);
banks->hash[alg_index].size = size;
r = ordered_set_ensure_put(prediction->results + json_variant_unsigned(nr), &tpm2_pcr_prediction_result_hash_ops, banks);
if (r == -EEXIST) /* Let's allow duplicates */
continue;
if (r < 0)
return log_error_errno(r, "Failed to insert result into set: %m");
TAKE_PTR(banks);
}
}
return 0;
}
int tpm2_calculate_policy_super_pcr(
Tpm2PCRPrediction *prediction,
uint16_t algorithm,
TPM2B_DIGEST *pcr_policy) {
int r;
assert_se(prediction);
assert_se(pcr_policy);
/* Start with a zero policy if not specified otheriwse */
TPM2B_DIGEST super_pcr_policy_digest = *pcr_policy;
/* First we look for all PCRs that have exactly one allowed hash value, and generate a single PolicyPCR policy from them */
_cleanup_free_ Tpm2PCRValue *single_values = NULL;
size_t n_single_values = 0;
for (uint32_t pcr = 0; pcr < TPM2_PCRS_MAX; pcr++) {
if (!FLAGS_SET(prediction->pcrs, UINT32_C(1) << pcr))
continue;
if (ordered_set_size(prediction->results[pcr]) != 1)
continue;
log_debug("Including PCR %" PRIu32 " in single value PolicyPCR expression", pcr);
Tpm2PCRPredictionResult *banks = ASSERT_PTR(ordered_set_first(prediction->results[pcr]));
TPM2B_DIGEST *hash = tpm2_pcr_prediction_result_get_hash(banks, algorithm);
if (!hash)
continue;
if (!GREEDY_REALLOC(single_values, n_single_values + 1))
return -ENOMEM;
single_values[n_single_values++] = TPM2_PCR_VALUE_MAKE(pcr, algorithm, *hash);
}
if (n_single_values > 0) {
/* Evolve policy based on the expected PCR value for what we found. */
r = tpm2_calculate_policy_pcr(
single_values,
n_single_values,
&super_pcr_policy_digest);
if (r < 0)
return r;
}
/* Now deal with the PCRs for which we have variants, i.e. more than one allowed values */
for (uint32_t pcr = 0; pcr < TPM2_PCRS_MAX; pcr++) {
_cleanup_free_ TPM2B_DIGEST *pcr_policy_digest_variants = NULL;
size_t n_pcr_policy_digest_variants = 0;
Tpm2PCRPredictionResult *banks;
if (!FLAGS_SET(prediction->pcrs, UINT32_C(1) << pcr))
continue;
if (ordered_set_size(prediction->results[pcr]) <= 1) /* We only care for PCRs with 2 or more variants in this loop */
continue;
if (ordered_set_size(prediction->results[pcr]) > 8)
return log_error_errno(SYNTHETIC_ERRNO(E2BIG), "PCR policies with more than 8 alternatives per PCR are currently not supported.");
ORDERED_SET_FOREACH(banks, prediction->results[pcr]) {
/* Start from the super PCR policy from the previous PCR we looked at so far. */
TPM2B_DIGEST pcr_policy_digest = super_pcr_policy_digest;
TPM2B_DIGEST *hash = tpm2_pcr_prediction_result_get_hash(banks, algorithm);
if (!hash)
continue;
/* Evolve it based on the expected PCR value for this PCR */
r = tpm2_calculate_policy_pcr(
&TPM2_PCR_VALUE_MAKE(
pcr,
algorithm,
*hash),
/* n_pcr_values= */ 1,
&pcr_policy_digest);
if (r < 0)
return r;
/* Store away this new variant */
if (!GREEDY_REALLOC(pcr_policy_digest_variants, n_pcr_policy_digest_variants + 1))
return log_oom();
pcr_policy_digest_variants[n_pcr_policy_digest_variants++] = pcr_policy_digest;
log_debug("Calculated PCR policy variant %zu for PCR %" PRIu32, n_pcr_policy_digest_variants, pcr);
}
assert_se(n_pcr_policy_digest_variants >= 2);
assert_se(n_pcr_policy_digest_variants <= 8);
/* Now combine all our variant into one OR policy */
r = tpm2_calculate_policy_or(
pcr_policy_digest_variants,
n_pcr_policy_digest_variants,
&super_pcr_policy_digest);
if (r < 0)
return r;
log_debug("Combined %zu variants in OR policy.", n_pcr_policy_digest_variants);
}
*pcr_policy = super_pcr_policy_digest;
return 0;
}
int tpm2_policy_super_pcr(
Tpm2Context *c,
const Tpm2Handle *session,
const Tpm2PCRPrediction *prediction,
uint16_t algorithm) {
int r;
assert_se(c);
assert_se(session);
assert_se(prediction);
TPM2B_DIGEST previous_policy_digest = TPM2B_DIGEST_MAKE(NULL, TPM2_SHA256_DIGEST_SIZE);
uint32_t single_value_pcrs = 0;
/* Look for all PCRs that have only a singled allowed hash value, and synthesize a single PolicyPCR policy item for them */
for (uint32_t pcr = 0; pcr < TPM2_PCRS_MAX; pcr++) {
if (!FLAGS_SET(prediction->pcrs, UINT32_C(1) << pcr))
continue;
if (ordered_set_size(prediction->results[pcr]) != 1)
continue;
log_debug("Including PCR %" PRIu32 " in single value PolicyPCR expression", pcr);
single_value_pcrs |= UINT32_C(1) << pcr;
}
if (single_value_pcrs != 0) {
TPML_PCR_SELECTION pcr_selection;
tpm2_tpml_pcr_selection_from_mask(single_value_pcrs, algorithm, &pcr_selection);
_cleanup_free_ TPM2B_DIGEST *current_policy_digest = NULL;
r = tpm2_policy_pcr(
c,
session,
&pcr_selection,
&current_policy_digest);
if (r < 0)
return r;
previous_policy_digest = *current_policy_digest;
}
for (uint32_t pcr = 0; pcr < TPM2_PCRS_MAX; pcr++) {
size_t n_branches;
if (!FLAGS_SET(prediction->pcrs, UINT32_C(1) << pcr))
continue;
n_branches = ordered_set_size(prediction->results[pcr]);
if (n_branches < 1 || n_branches > 8)
return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Number of variants per PCR not in range 1…8");
if (n_branches == 1) /* Single choice PCRs are already covered by the loop above */
continue;
log_debug("Submitting PCR/OR policy for PCR %" PRIu32, pcr);
TPML_PCR_SELECTION pcr_selection;
tpm2_tpml_pcr_selection_from_mask(UINT32_C(1) << pcr, algorithm, &pcr_selection);
_cleanup_free_ TPM2B_DIGEST *current_policy_digest = NULL;
r = tpm2_policy_pcr(
c,
session,
&pcr_selection,
&current_policy_digest);
if (r < 0)
return r;
_cleanup_free_ TPM2B_DIGEST *branches = NULL;
branches = new0(TPM2B_DIGEST, n_branches);
if (!branches)
return log_oom();
Tpm2PCRPredictionResult *banks;
size_t i = 0;
ORDERED_SET_FOREACH(banks, prediction->results[pcr]) {
TPM2B_DIGEST pcr_policy_digest = previous_policy_digest;
TPM2B_DIGEST *hash = tpm2_pcr_prediction_result_get_hash(banks, algorithm);
if (!hash)
continue;
/* Evolve it based on the expected PCR value for this PCR */
r = tpm2_calculate_policy_pcr(
&TPM2_PCR_VALUE_MAKE(
pcr,
algorithm,
*hash),
/* n_pcr_values= */ 1,
&pcr_policy_digest);
if (r < 0)
return r;
branches[i++] = pcr_policy_digest;
}
assert_se(i == n_branches);
current_policy_digest = mfree(current_policy_digest);
r = tpm2_policy_or(
c,
session,
branches,
n_branches,
&current_policy_digest);
if (r < 0)
return r;
previous_policy_digest = *current_policy_digest;
}
return 0;
}
void tpm2_pcrlock_policy_done(Tpm2PCRLockPolicy *data) {
assert(data);
data->prediction_json = json_variant_unref(data->prediction_json);
tpm2_pcr_prediction_done(&data->prediction);
iovec_done(&data->nv_handle);
iovec_done(&data->nv_public);
iovec_done(&data->srk_handle);
iovec_done(&data->pin_public);
iovec_done(&data->pin_private);
}
static int json_dispatch_tpm2_algorithm(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) {
uint16_t *algorithm = ASSERT_PTR(userdata);
int r;
r = tpm2_hash_alg_from_string(json_variant_string(variant));
if (r < 0 || tpm2_hash_algorithm_index(r) == SIZE_MAX)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid hash algorithm: %s", json_variant_string(variant));
*algorithm = r;
return 0;
}
int tpm2_pcrlock_search_file(const char *path, FILE **ret_file, char **ret_path) {
static const char search[] =
"/run/systemd\0"
"/var/lib/systemd\0";
int r;
if (!path)
path = "pcrlock.json";
r = search_and_fopen_nulstr(path, ret_file ? "re" : NULL, NULL, search, ret_file, ret_path);
if (r < 0)
return log_debug_errno(r, "Failed to find TPM2 pcrlock policy file '%s': %m", path);
return 0;
}
int tpm2_pcrlock_policy_load(
const char *path,
Tpm2PCRLockPolicy *ret_policy) {
_cleanup_free_ char *discovered_path = NULL;
_cleanup_fclose_ FILE *f = NULL;
int r;
r = tpm2_pcrlock_search_file(path, &f, &discovered_path);
if (r == -ENOENT) {
*ret_policy = (Tpm2PCRLockPolicy) {};
return 0;
}
if (r < 0)
return log_error_errno(r, "Failed to load TPM2 pcrlock policy file: %m");
_cleanup_(json_variant_unrefp) JsonVariant *configuration_json = NULL;
r = json_parse_file(
f,
discovered_path,
/* flags = */ 0,
&configuration_json,
/* ret_line= */ NULL,
/* ret_column= */ NULL);
if (r < 0)
return log_error_errno(r, "Failed to parse existing pcrlock policy file '%s': %m", discovered_path);
JsonDispatch policy_dispatch[] = {
{ "pcrBank", JSON_VARIANT_STRING, json_dispatch_tpm2_algorithm, offsetof(Tpm2PCRLockPolicy, algorithm), JSON_MANDATORY },
{ "pcrValues", JSON_VARIANT_ARRAY, json_dispatch_variant, offsetof(Tpm2PCRLockPolicy, prediction_json), JSON_MANDATORY },
{ "nvIndex", JSON_VARIANT_INTEGER, json_dispatch_uint32, offsetof(Tpm2PCRLockPolicy, nv_index), JSON_MANDATORY },
{ "nvHandle", JSON_VARIANT_STRING, json_dispatch_unbase64_iovec, offsetof(Tpm2PCRLockPolicy, nv_handle), JSON_MANDATORY },
{ "nvPublic", JSON_VARIANT_STRING, json_dispatch_unbase64_iovec, offsetof(Tpm2PCRLockPolicy, nv_public), JSON_MANDATORY },
{ "srkHandle", JSON_VARIANT_STRING, json_dispatch_unbase64_iovec, offsetof(Tpm2PCRLockPolicy, srk_handle), JSON_MANDATORY },
{ "pinPublic", JSON_VARIANT_STRING, json_dispatch_unbase64_iovec, offsetof(Tpm2PCRLockPolicy, pin_public), JSON_MANDATORY },
{ "pinPrivate", JSON_VARIANT_STRING, json_dispatch_unbase64_iovec, offsetof(Tpm2PCRLockPolicy, pin_private), JSON_MANDATORY },
{}
};
_cleanup_(tpm2_pcrlock_policy_done) Tpm2PCRLockPolicy policy = {};
r = json_dispatch(configuration_json, policy_dispatch, JSON_LOG, &policy);
if (r < 0)
return r;
r = tpm2_pcr_prediction_from_json(&policy.prediction, policy.algorithm, policy.prediction_json);
if (r < 0)
return r;
*ret_policy = TAKE_STRUCT(policy);
return 1;
}
#endif
char *tpm2_pcr_mask_to_string(uint32_t mask) { char *tpm2_pcr_mask_to_string(uint32_t mask) {
_cleanup_free_ char *s = NULL; _cleanup_free_ char *s = NULL;
@ -5550,6 +6161,7 @@ int tpm2_make_luks2_json(
JSON_BUILD_PAIR_CONDITION(!!tpm2_asym_alg_to_string(primary_alg), "tpm2-primary-alg", JSON_BUILD_STRING(tpm2_asym_alg_to_string(primary_alg))), JSON_BUILD_PAIR_CONDITION(!!tpm2_asym_alg_to_string(primary_alg), "tpm2-primary-alg", JSON_BUILD_STRING(tpm2_asym_alg_to_string(primary_alg))),
JSON_BUILD_PAIR("tpm2-policy-hash", JSON_BUILD_HEX(policy_hash, policy_hash_size)), 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("tpm2-pcrlock", JSON_BUILD_BOOLEAN(flags & TPM2_FLAGS_USE_PCRLOCK)),
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_pcrs", JSON_BUILD_VARIANT(pkmj)),
JSON_BUILD_PAIR_CONDITION(pubkey_pcr_mask != 0, "tpm2_pubkey", JSON_BUILD_BASE64(pubkey, pubkey_size)), JSON_BUILD_PAIR_CONDITION(pubkey_pcr_mask != 0, "tpm2_pubkey", JSON_BUILD_BASE64(pubkey, pubkey_size)),
JSON_BUILD_PAIR_CONDITION(salt, "tpm2_salt", JSON_BUILD_BASE64(salt, salt_size)), JSON_BUILD_PAIR_CONDITION(salt, "tpm2_salt", JSON_BUILD_BASE64(salt, salt_size)),
@ -5668,6 +6280,14 @@ int tpm2_parse_luks2_json(
SET_FLAG(flags, TPM2_FLAGS_USE_PIN, json_variant_boolean(w)); SET_FLAG(flags, TPM2_FLAGS_USE_PIN, json_variant_boolean(w));
} }
w = json_variant_by_key(v, "tpm2-pcrlock");
if (w) {
if (!json_variant_is_boolean(w))
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "TPM2 pclock policy is not a boolean.");
SET_FLAG(flags, TPM2_FLAGS_USE_PCRLOCK, json_variant_boolean(w));
}
w = json_variant_by_key(v, "tpm2_salt"); w = json_variant_by_key(v, "tpm2_salt");
if (w) { if (w) {
r = json_variant_unbase64(w, &salt, &salt_size); r = json_variant_unbase64(w, &salt, &salt_size);

View File

@ -8,11 +8,13 @@
#include "json.h" #include "json.h"
#include "macro.h" #include "macro.h"
#include "openssl-util.h" #include "openssl-util.h"
#include "ordered-set.h"
#include "sha256.h" #include "sha256.h"
#include "tpm2-pcr.h" #include "tpm2-pcr.h"
typedef enum TPM2Flags { typedef enum TPM2Flags {
TPM2_FLAGS_USE_PIN = 1 << 0, TPM2_FLAGS_USE_PIN = 1 << 0,
TPM2_FLAGS_USE_PCRLOCK = 1 << 1,
} TPM2Flags; } TPM2Flags;
/* As per https://trustedcomputinggroup.org/wp-content/uploads/TCG_PCClient_PFP_r1p05_v23_pub.pdf a /* As per https://trustedcomputinggroup.org/wp-content/uploads/TCG_PCClient_PFP_r1p05_v23_pub.pdf a
@ -192,6 +194,52 @@ void tpm2_log_debug_buffer(const void *buffer, size_t size, const char *msg);
void tpm2_log_debug_digest(const TPM2B_DIGEST *digest, const char *msg); void tpm2_log_debug_digest(const TPM2B_DIGEST *digest, const char *msg);
void tpm2_log_debug_name(const TPM2B_NAME *name, const char *msg); void tpm2_log_debug_name(const TPM2B_NAME *name, const char *msg);
typedef struct Tpm2PCRPredictionResult {
TPM2B_DIGEST hash[TPM2_N_HASH_ALGORITHMS]; /* a hash for each potential algorithm */
} Tpm2PCRPredictionResult;
TPM2B_DIGEST *tpm2_pcr_prediction_result_get_hash(Tpm2PCRPredictionResult *result, uint16_t alg);
/* A structure encapsulating a full set of PCR predictions with alternatives. This can be converted into a
* series of PolicyOR + PolicyPCR items for the TPM. */
typedef struct Tpm2PCRPrediction {
uint32_t pcrs; /* A mask of pcrs included */
OrderedSet* results[TPM2_PCRS_MAX]; /* set of Tpm2PCRPredictionResult objects, one for each PCR */
} Tpm2PCRPrediction;
void tpm2_pcr_prediction_done(Tpm2PCRPrediction *p);
extern const struct hash_ops tpm2_pcr_prediction_result_hash_ops;
bool tpm2_pcr_prediction_equal(Tpm2PCRPrediction *a, Tpm2PCRPrediction *b, uint16_t algorithm);
int tpm2_pcr_prediction_to_json(const Tpm2PCRPrediction *prediction, uint16_t algorithm, JsonVariant **ret);
int tpm2_pcr_prediction_from_json(Tpm2PCRPrediction *prediction, uint16_t algorithm, JsonVariant *aj);
/* As structure encapsulating all metadata stored for a pcrlock policy on disk */
typedef struct Tpm2PCRLockPolicy {
/* The below is the fixed metadata encoding information about the NV index we store the
* PolicyAuthorizeNV policy in, as well as a pinned SRK, and the encrypted PIN to use for writing to
* the NV Index. */
uint16_t algorithm;
uint32_t nv_index;
struct iovec nv_handle;
struct iovec nv_public;
struct iovec srk_handle;
struct iovec pin_public;
struct iovec pin_private;
/* The below contains the current prediction whose resulting policy is stored in the NV
* index. Once in JSON and once in parsed form. When the policy is updated the fields below are
* changed, the fields above remain fixed. */
JsonVariant *prediction_json;
Tpm2PCRPrediction prediction;
} Tpm2PCRLockPolicy;
void tpm2_pcrlock_policy_done(Tpm2PCRLockPolicy *data);
int tpm2_pcrlock_search_file(const char *path, FILE **ret_file, char **ret_path);
int tpm2_pcrlock_policy_load(const char *path, Tpm2PCRLockPolicy *ret_policy);
int tpm2_index_to_handle(Tpm2Context *c, TPM2_HANDLE index, const Tpm2Handle *session, TPM2B_PUBLIC **ret_public, TPM2B_NAME **ret_name, TPM2B_NAME **ret_qname, Tpm2Handle **ret_handle); int tpm2_index_to_handle(Tpm2Context *c, TPM2_HANDLE index, const Tpm2Handle *session, TPM2B_PUBLIC **ret_public, TPM2B_NAME **ret_name, TPM2B_NAME **ret_qname, Tpm2Handle **ret_handle);
int tpm2_index_from_handle(Tpm2Context *c, const Tpm2Handle *handle, TPM2_HANDLE *ret_index); int tpm2_index_from_handle(Tpm2Context *c, const Tpm2Handle *handle, TPM2_HANDLE *ret_index);
@ -208,6 +256,7 @@ int tpm2_policy_auth_value(Tpm2Context *c, const Tpm2Handle *session, TPM2B_DIGE
int tpm2_policy_authorize_nv(Tpm2Context *c, const Tpm2Handle *session, const Tpm2Handle *nv_handle, TPM2B_DIGEST **ret_policy_digest); int tpm2_policy_authorize_nv(Tpm2Context *c, const Tpm2Handle *session, const Tpm2Handle *nv_handle, TPM2B_DIGEST **ret_policy_digest);
int tpm2_policy_pcr(Tpm2Context *c, const Tpm2Handle *session, const TPML_PCR_SELECTION *pcr_selection, TPM2B_DIGEST **ret_policy_digest); int tpm2_policy_pcr(Tpm2Context *c, const Tpm2Handle *session, const TPML_PCR_SELECTION *pcr_selection, TPM2B_DIGEST **ret_policy_digest);
int tpm2_policy_or(Tpm2Context *c, const Tpm2Handle *session, const TPM2B_DIGEST *branches, size_t n_branches, TPM2B_DIGEST **ret_policy_digest); int tpm2_policy_or(Tpm2Context *c, const Tpm2Handle *session, const TPM2B_DIGEST *branches, size_t n_branches, TPM2B_DIGEST **ret_policy_digest);
int tpm2_policy_super_pcr(Tpm2Context *c, const Tpm2Handle *session, const Tpm2PCRPrediction *prediction, uint16_t algorithm);
int tpm2_calculate_pubkey_name(const TPMT_PUBLIC *public, TPM2B_NAME *ret_name); int tpm2_calculate_pubkey_name(const TPMT_PUBLIC *public, TPM2B_NAME *ret_name);
int tpm2_calculate_nv_index_name(const TPMS_NV_PUBLIC *nvpublic, TPM2B_NAME *ret_name); int tpm2_calculate_nv_index_name(const TPMS_NV_PUBLIC *nvpublic, TPM2B_NAME *ret_name);
@ -217,12 +266,13 @@ int tpm2_calculate_policy_authorize(const TPM2B_PUBLIC *public, const TPM2B_DIGE
int tpm2_calculate_policy_authorize_nv(const TPM2B_NV_PUBLIC *public, TPM2B_DIGEST *digest); int tpm2_calculate_policy_authorize_nv(const TPM2B_NV_PUBLIC *public, TPM2B_DIGEST *digest);
int tpm2_calculate_policy_pcr(const Tpm2PCRValue *pcr_values, size_t n_pcr_values, TPM2B_DIGEST *digest); int tpm2_calculate_policy_pcr(const Tpm2PCRValue *pcr_values, size_t n_pcr_values, TPM2B_DIGEST *digest);
int tpm2_calculate_policy_or(const TPM2B_DIGEST *branches, size_t n_branches, TPM2B_DIGEST *digest); int tpm2_calculate_policy_or(const TPM2B_DIGEST *branches, size_t n_branches, TPM2B_DIGEST *digest);
int tpm2_calculate_sealing_policy(const Tpm2PCRValue *pcr_values, size_t n_pcr_values, const TPM2B_PUBLIC *public, bool use_pin, TPM2B_DIGEST *digest); int tpm2_calculate_policy_super_pcr(Tpm2PCRPrediction *prediction, uint16_t algorithm, TPM2B_DIGEST *pcr_policy);
int tpm2_calculate_sealing_policy(const Tpm2PCRValue *pcr_values, size_t n_pcr_values, const TPM2B_PUBLIC *public, bool use_pin, const Tpm2PCRLockPolicy *policy, TPM2B_DIGEST *digest);
int tpm2_get_or_create_srk(Tpm2Context *c, const Tpm2Handle *session, TPM2B_PUBLIC **ret_public, TPM2B_NAME **ret_name, TPM2B_NAME **ret_qname, Tpm2Handle **ret_handle); int tpm2_get_or_create_srk(Tpm2Context *c, const Tpm2Handle *session, TPM2B_PUBLIC **ret_public, TPM2B_NAME **ret_name, TPM2B_NAME **ret_qname, Tpm2Handle **ret_handle);
int tpm2_seal(Tpm2Context *c, uint32_t seal_key_handle, const TPM2B_DIGEST *policy, const char *pin, void **ret_secret, size_t *ret_secret_size, void **ret_blob, size_t *ret_blob_size, uint16_t *ret_primary_alg, void **ret_srk_buf, size_t *ret_srk_buf_size); int tpm2_seal(Tpm2Context *c, uint32_t seal_key_handle, const TPM2B_DIGEST *policy, const char *pin, void **ret_secret, size_t *ret_secret_size, void **ret_blob, size_t *ret_blob_size, uint16_t *ret_primary_alg, void **ret_srk_buf, size_t *ret_srk_buf_size);
int tpm2_unseal(Tpm2Context *c, 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, const void *srk_buf, size_t srk_buf_size, void **ret_secret, size_t *ret_secret_size); int tpm2_unseal(Tpm2Context *c, 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, const Tpm2PCRLockPolicy *pcrlock_policy, uint16_t primary_alg, const void *blob, size_t blob_size, const void *policy_hash, size_t policy_hash_size, const void *srk_buf, size_t srk_buf_size, void **ret_secret, size_t *ret_secret_size);
#if HAVE_OPENSSL #if HAVE_OPENSSL
int tpm2_tpm2b_public_to_openssl_pkey(const TPM2B_PUBLIC *public, EVP_PKEY **ret); int tpm2_tpm2b_public_to_openssl_pkey(const TPM2B_PUBLIC *public, EVP_PKEY **ret);
@ -304,6 +354,11 @@ typedef struct {} Tpm2Handle;
typedef struct {} Tpm2PCRValue; typedef struct {} Tpm2PCRValue;
#define TPM2_PCR_VALUE_MAKE(i, h, v) (Tpm2PCRValue) {} #define TPM2_PCR_VALUE_MAKE(i, h, v) (Tpm2PCRValue) {}
static inline int tpm2_pcrlock_search_file(const char *path, FILE **ret_file, char **ret_path) {
return -ENOENT;
}
#endif /* HAVE_TPM2 */ #endif /* HAVE_TPM2 */
int tpm2_list_devices(void); int tpm2_list_devices(void);
@ -361,6 +416,7 @@ typedef struct {
uint32_t search_pcr_mask; uint32_t search_pcr_mask;
const char *device; const char *device;
const char *signature_path; const char *signature_path;
const char *pcrlock_path;
} systemd_tpm2_plugin_params; } systemd_tpm2_plugin_params;
typedef enum Tpm2Support { typedef enum Tpm2Support {

View File

@ -1043,6 +1043,7 @@ static void check_seal_unseal_for_handle(Tpm2Context *c, TPM2_HANDLE handle) {
/* pubkey_pcr_mask= */ 0, /* pubkey_pcr_mask= */ 0,
/* signature= */ NULL, /* signature= */ NULL,
/* pin= */ NULL, /* pin= */ NULL,
/* pcrlock_policy= */ NULL,
/* primary_alg= */ 0, /* primary_alg= */ 0,
blob, blob_size, blob, blob_size,
/* policy_hash= */ NULL, /* policy_hash_size= */ 0, /* policy_hash= */ NULL, /* policy_hash_size= */ 0,