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:
parent
b52e950598
commit
a434270139
@ -2174,6 +2174,7 @@ subdir('src/oom')
|
||||
subdir('src/partition')
|
||||
subdir('src/path')
|
||||
subdir('src/pcrextend')
|
||||
subdir('src/pcrlock')
|
||||
subdir('src/portable')
|
||||
subdir('src/pstore')
|
||||
subdir('src/quotacheck')
|
||||
|
@ -248,6 +248,7 @@ int enroll_tpm2(struct crypt_device *cd,
|
||||
n_hash_pcr_values,
|
||||
pubkey ? &public : NULL,
|
||||
use_pin,
|
||||
/* pcrlock_policy= */ NULL,
|
||||
&policy);
|
||||
if (r < 0)
|
||||
return r;
|
||||
@ -288,6 +289,7 @@ int enroll_tpm2(struct crypt_device *cd,
|
||||
pubkey_pcr_mask,
|
||||
signature_json,
|
||||
pin_str,
|
||||
/* pcrlock_policy= */ NULL,
|
||||
/* primary_alg= */ 0,
|
||||
blob, blob_size,
|
||||
policy.buffer, policy.size,
|
||||
|
@ -88,6 +88,7 @@ int acquire_luks2_key(
|
||||
pubkey_pcr_mask,
|
||||
signature_json,
|
||||
pin,
|
||||
/* pcrlock_policy= */ NULL,
|
||||
primary_alg,
|
||||
key_data, key_data_size,
|
||||
policy_hash, policy_hash_size,
|
||||
|
@ -142,6 +142,7 @@ int acquire_tpm2_key(
|
||||
pubkey_pcr_mask,
|
||||
signature_json,
|
||||
/* pin= */ NULL,
|
||||
/* pcrlock_policy= */ NULL,
|
||||
primary_alg,
|
||||
blob,
|
||||
blob_size,
|
||||
@ -189,6 +190,7 @@ int acquire_tpm2_key(
|
||||
pubkey_pcr_mask,
|
||||
signature_json,
|
||||
b64_salted_pin,
|
||||
/* pcrlock_policy= */ NULL,
|
||||
primary_alg,
|
||||
blob,
|
||||
blob_size,
|
||||
|
@ -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);
|
||||
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)
|
||||
return log_error_errno(r, "Could not calculate sealing policy digest: %m");
|
||||
|
||||
|
21
src/pcrlock/meson.build
Normal file
21
src/pcrlock/meson.build
Normal 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,
|
||||
],
|
||||
},
|
||||
]
|
168
src/pcrlock/pcrlock-firmware.c
Normal file
168
src/pcrlock/pcrlock-firmware.c
Normal 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;
|
||||
}
|
25
src/pcrlock/pcrlock-firmware.h
Normal file
25
src/pcrlock/pcrlock-firmware.h
Normal 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
4992
src/pcrlock/pcrlock.c
Normal file
File diff suppressed because it is too large
Load Diff
246
src/pcrlock/pehash.c
Normal file
246
src/pcrlock/pehash.c
Normal 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, §ions);
|
||||
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, §ions);
|
||||
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
11
src/pcrlock/pehash.h
Normal 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);
|
@ -856,6 +856,7 @@ int encrypt_credential_and_warn(
|
||||
tpm2_n_hash_pcr_values,
|
||||
pubkey ? &public : NULL,
|
||||
/* use_pin= */ false,
|
||||
/* pcrlock_policy= */ NULL,
|
||||
&tpm2_policy);
|
||||
if (r < 0)
|
||||
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,
|
||||
signature_json,
|
||||
/* pin= */ NULL,
|
||||
/* pcrlock_policy= */ NULL,
|
||||
le16toh(t->primary_alg),
|
||||
t->policy_hash_and_blob,
|
||||
le32toh(t->blob_size),
|
||||
|
@ -3906,6 +3906,7 @@ int tpm2_calculate_sealing_policy(
|
||||
size_t n_pcr_values,
|
||||
const TPM2B_PUBLIC *public,
|
||||
bool use_pin,
|
||||
const Tpm2PCRLockPolicy *pcrlock_policy,
|
||||
TPM2B_DIGEST *digest) {
|
||||
|
||||
int r;
|
||||
@ -3913,12 +3914,30 @@ int tpm2_calculate_sealing_policy(
|
||||
assert(pcr_values || n_pcr_values == 0);
|
||||
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) {
|
||||
r = tpm2_calculate_policy_authorize(public, NULL, digest);
|
||||
if (r < 0)
|
||||
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) {
|
||||
r = tpm2_calculate_policy_pcr(pcr_values, n_pcr_values, digest);
|
||||
if (r < 0)
|
||||
@ -3945,6 +3964,7 @@ static int tpm2_build_sealing_policy(
|
||||
uint32_t pubkey_pcr_mask,
|
||||
JsonVariant *signature_json,
|
||||
bool use_pin,
|
||||
const Tpm2PCRLockPolicy *pcrlock_policy,
|
||||
TPM2B_DIGEST **ret_policy_digest) {
|
||||
|
||||
int r;
|
||||
@ -3963,6 +3983,9 @@ static int tpm2_build_sealing_policy(
|
||||
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) {
|
||||
TPML_PCR_SELECTION 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;
|
||||
}
|
||||
|
||||
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) {
|
||||
TPML_PCR_SELECTION 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,
|
||||
JsonVariant *signature,
|
||||
const char *pin,
|
||||
const Tpm2PCRLockPolicy *pcrlock_policy,
|
||||
uint16_t primary_alg,
|
||||
const void *blob,
|
||||
size_t blob_size,
|
||||
@ -4695,6 +4747,7 @@ int tpm2_unseal(Tpm2Context *c,
|
||||
pubkey_pcr_mask,
|
||||
signature,
|
||||
!!pin,
|
||||
pcrlock_policy,
|
||||
&policy_digest);
|
||||
if (r < 0)
|
||||
return r;
|
||||
@ -5417,7 +5470,6 @@ int tpm2_extend_bytes(
|
||||
return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "OpenSSL support is disabled.");
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
const uint16_t tpm2_hash_algorithms[] = {
|
||||
TPM2_ALG_SHA1,
|
||||
@ -5429,6 +5481,565 @@ const uint16_t tpm2_hash_algorithms[] = {
|
||||
|
||||
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,
|
||||
¤t_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,
|
||||
¤t_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,
|
||||
¤t_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) {
|
||||
_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("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-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", JSON_BUILD_BASE64(pubkey, pubkey_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));
|
||||
}
|
||||
|
||||
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");
|
||||
if (w) {
|
||||
r = json_variant_unbase64(w, &salt, &salt_size);
|
||||
|
@ -8,11 +8,13 @@
|
||||
#include "json.h"
|
||||
#include "macro.h"
|
||||
#include "openssl-util.h"
|
||||
#include "ordered-set.h"
|
||||
#include "sha256.h"
|
||||
#include "tpm2-pcr.h"
|
||||
|
||||
typedef enum TPM2Flags {
|
||||
TPM2_FLAGS_USE_PIN = 1 << 0,
|
||||
TPM2_FLAGS_USE_PIN = 1 << 0,
|
||||
TPM2_FLAGS_USE_PCRLOCK = 1 << 1,
|
||||
} TPM2Flags;
|
||||
|
||||
/* 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_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_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_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_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_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_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_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_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
|
||||
int tpm2_tpm2b_public_to_openssl_pkey(const TPM2B_PUBLIC *public, EVP_PKEY **ret);
|
||||
@ -304,6 +354,11 @@ typedef struct {} Tpm2Handle;
|
||||
typedef struct {} 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 */
|
||||
|
||||
int tpm2_list_devices(void);
|
||||
@ -361,6 +416,7 @@ typedef struct {
|
||||
uint32_t search_pcr_mask;
|
||||
const char *device;
|
||||
const char *signature_path;
|
||||
const char *pcrlock_path;
|
||||
} systemd_tpm2_plugin_params;
|
||||
|
||||
typedef enum Tpm2Support {
|
||||
|
@ -1043,6 +1043,7 @@ static void check_seal_unseal_for_handle(Tpm2Context *c, TPM2_HANDLE handle) {
|
||||
/* pubkey_pcr_mask= */ 0,
|
||||
/* signature= */ NULL,
|
||||
/* pin= */ NULL,
|
||||
/* pcrlock_policy= */ NULL,
|
||||
/* primary_alg= */ 0,
|
||||
blob, blob_size,
|
||||
/* policy_hash= */ NULL, /* policy_hash_size= */ 0,
|
||||
|
Loading…
Reference in New Issue
Block a user