libimcv: Evaluate IMA SHA-256 measurements

This commit is contained in:
Andreas Steffen 2020-12-17 12:14:23 +01:00
parent 839d6c8f80
commit 9b4a2322d6
4 changed files with 231 additions and 115 deletions

View File

@ -47,3 +47,7 @@ libimcv.plugins.imc-attestation.pcr18_meas =
libimcv.plugins.imc-attestation.pcr18_after =
PCR18 value after measurement.
libimcv.plugins.imc-attestation.pcr_padding = no
Whether to pad IMA SHA1 measurements values when extending into
SHA256 PCR bank.

View File

@ -29,7 +29,6 @@
#define SECURITY_DIR "/sys/kernel/security/"
#define IMA_BIOS_MEASUREMENTS SECURITY_DIR "tpm0/binary_bios_measurements"
#define IMA_RUNTIME_MEASUREMENTS SECURITY_DIR "ima/binary_runtime_measurements"
#define IMA_FILENAME_LEN_MAX 255
typedef struct pts_ita_comp_ima_t pts_ita_comp_ima_t;
typedef enum ima_state_t ima_state_t;
@ -118,6 +117,11 @@ struct pts_ita_comp_ima_t {
*/
bool pcr_info;
/**
* Whether to pad PCR measurements if matching hash is not available
*/
bool pcr_padding;
/**
* Creation time of measurement
*/
@ -165,16 +169,15 @@ struct pts_ita_comp_ima_t {
*/
static pts_comp_evidence_t* extend_pcr(pts_ita_comp_ima_t* this,
uint8_t qualifier, pts_pcr_t *pcrs,
uint32_t pcr, chunk_t measurement)
uint32_t pcr, chunk_t measurement,
pts_pcr_transform_t pcr_transform)
{
pts_pcr_transform_t pcr_transform;
pts_meas_algorithms_t pcr_algo;
pts_comp_func_name_t *name;
pts_comp_evidence_t *evidence;
chunk_t pcr_before = chunk_empty, pcr_after = chunk_empty;
pcr_algo = pcrs->get_pcr_algo(pcrs);
pcr_transform = PTS_PCR_TRANSFORM_MATCH;
if (this->pcr_info)
{
@ -199,24 +202,31 @@ static pts_comp_evidence_t* extend_pcr(pts_ita_comp_ima_t* this,
}
/**
* Generate an IMA or IMA-NG hash from an event digest and event name
*
* @param digest event digest
* @param ima_algo hash algorithm string ("sha1:", "sha256:", etc.)
* @param ima_name event name
* @param little_endian endianness of client platform
* @param algo hash algorithm used by TPM
* @param hash_buf hash value to be compared with TPM measurement
* Compute and check boot aggregate value by hashing PCR0 to PCR7
*/
static bool ima_hash(chunk_t digest, char *ima_algo, char *ima_name,
bool little_endian, pts_meas_algorithms_t algo,
char *hash_buf)
static bool check_boot_aggregate(pts_pcr_t *pcrs, char *algo, bool pcr_padding,
chunk_t boot_aggregate, chunk_t measurement)
{
chunk_t ba_measurement;
uint8_t meas_buffer[HASH_SIZE_SHA512];
size_t hash_size;
pts_meas_algorithms_t pcr_algo;
hash_algorithm_t hash_alg;
hasher_t *hasher;
bool success;
uint32_t i, pcr_max;
bool success, pcr_ok = TRUE;
hash_alg = pts_meas_algo_to_hash(algo);
/* determine PCR hash algorithm and the need for PCR padding */
pcr_algo = pcrs->get_pcr_algo(pcrs);
if (pcr_algo == PTS_MEAS_ALGO_SHA1)
{
pcr_padding = FALSE;
}
/* create hasher for boot aggregate computation */
hash_alg = pts_meas_algo_to_hash(pcr_algo);
hasher = lib->crypto->create_hasher(lib->crypto, hash_alg);
if (!hasher)
{
@ -224,90 +234,39 @@ static bool ima_hash(chunk_t digest, char *ima_algo, char *ima_name,
hash_algorithm_short_names, hash_alg);
return FALSE;
}
hash_size = hasher->get_hash_size(hasher);
if (ima_algo)
{
uint32_t d_len, n_len;
chunk_t algo_name, event_name, digest_len, name_len;
/* Include PCR8 and PCR9 in boot aggregate with unpadded non-SHA1 hashes */
pcr_max = (pcr_algo == PTS_MEAS_ALGO_SHA1 || pcr_padding) ? 7 : 9;
/* IMA-NG hash */
algo_name = chunk_create(ima_algo, strlen(ima_algo) + 1);
event_name = chunk_create(ima_name, strlen(ima_name) + 1);
d_len = algo_name.len + digest.len;
digest_len = chunk_create((uint8_t*)&d_len, sizeof(d_len));
/* TODO handle endianness of both client and server platforms */
n_len = event_name.len;
name_len = chunk_create((uint8_t*)&n_len, sizeof(n_len));
/* TODO handle endianness of both client and server platforms */
success = hasher->get_hash(hasher, digest_len, NULL) &&
hasher->get_hash(hasher, algo_name, NULL) &&
hasher->get_hash(hasher, digest, NULL) &&
hasher->get_hash(hasher, name_len, NULL) &&
hasher->get_hash(hasher, event_name, hash_buf);
}
else
{
u_char filename_buffer[IMA_FILENAME_LEN_MAX + 1];
chunk_t file_name;
/* IMA legacy hash */
memset(filename_buffer, 0, sizeof(filename_buffer));
strncpy(filename_buffer, ima_name, IMA_FILENAME_LEN_MAX);
file_name = chunk_create (filename_buffer, sizeof(filename_buffer));
success = hasher->get_hash(hasher, digest, NULL) &&
hasher->get_hash(hasher, file_name, hash_buf);
}
hasher->destroy(hasher);
return success;
}
/**
* Compute and check boot aggregate value by hashing PCR0 to PCR7
*/
static bool check_boot_aggregate(pts_pcr_t *pcrs, chunk_t measurement,
char *algo)
{
u_char pcr_buffer[HASH_SIZE_SHA1];
chunk_t boot_aggregate;
hasher_t *hasher;
uint32_t i;
bool success, pcr_ok = TRUE;
hasher = lib->crypto->create_hasher(lib->crypto, HASH_SHA1);
if (!hasher)
{
DBG1(DBG_PTS, "%N hasher could not be created",
hash_algorithm_short_names, HASH_SHA1);
return FALSE;
}
for (i = 0; i < 8 && pcr_ok; i++)
/* the boot aggregate hash is computed over PCR0 .. PCR7/PCR9 */
for (i = 0; i <= pcr_max && pcr_ok; i++)
{
pcr_ok = hasher->get_hash(hasher, pcrs->get(pcrs, i), NULL);
}
if (pcr_ok)
{
pcr_ok = hasher->get_hash(hasher, chunk_empty, pcr_buffer);
pcr_ok = hasher->get_hash(hasher, chunk_empty, boot_aggregate.ptr);
}
hasher->destroy(hasher);
if (pcr_ok)
{
boot_aggregate = chunk_create(pcr_buffer, sizeof(pcr_buffer));
/* TODO handle endianness of client platform */
pcr_ok = ima_hash(boot_aggregate, algo, "boot_aggregate",
TRUE, PTS_MEAS_ALGO_SHA1, pcr_buffer);
ba_measurement = chunk_create(meas_buffer, hash_size);
if (pcr_padding)
{
memset(meas_buffer, 0x00, hash_size);
pcr_algo = PTS_MEAS_ALGO_SHA1;
}
pcr_ok = pts_ima_event_hash(boot_aggregate, algo, "boot_aggregate",
pcr_algo, meas_buffer);
}
if (pcr_ok)
{
success = chunk_equals_const(boot_aggregate, measurement);
DBG1(DBG_PTS, "boot aggregate value is %scorrect",
success ? "":"in");
success = chunk_equals_const(ba_measurement, measurement);
DBG1(DBG_PTS, "boot aggregate computed over PCR0..PCR%d is %scorrect",
pcr_max, success ? "":"in");
return success;
}
else
@ -340,9 +299,11 @@ METHOD(pts_component_t, measure, status_t,
pts_comp_evidence_t **evidence)
{
pts_pcr_t *pcrs;
pts_meas_algorithms_t pcr_algo;
pts_comp_evidence_t *evid = NULL;
size_t algo_len, name_len;
chunk_t measurement;
size_t algo_len, name_len, pcr_size;
chunk_t measurement, boot_aggregate;
uint8_t pcr_buffer[HASH_SIZE_SHA512];
char *uri, *algo, *name;
uint32_t pcr;
status_t status;
@ -352,6 +313,8 @@ METHOD(pts_component_t, measure, status_t,
{
return FAILED;
}
pcr_algo = pcrs->get_pcr_algo(pcrs);
pcr_size = pts_meas_algo_hash_size(pcr_algo);
if (qualifier == (PTS_ITA_QUALIFIER_FLAG_KERNEL |
PTS_ITA_QUALIFIER_TYPE_TRUSTED))
@ -360,8 +323,7 @@ METHOD(pts_component_t, measure, status_t,
{
case IMA_STATE_INIT:
this->bios_list = pts_ima_bios_list_create(pts->get_tpm(pts),
IMA_BIOS_MEASUREMENTS,
pcrs->get_pcr_algo(pcrs));
IMA_BIOS_MEASUREMENTS, pcr_algo);
if (!this->bios_list)
{
return FAILED;
@ -378,7 +340,8 @@ METHOD(pts_component_t, measure, status_t,
DBG1(DBG_PTS, "could not retrieve bios measurement entry");
return status;
}
evid = extend_pcr(this, qualifier, pcrs, pcr, measurement);
evid = extend_pcr(this, qualifier, pcrs, pcr, measurement,
PTS_PCR_TRANSFORM_MATCH);
this->state = this->bios_list->get_count(this->bios_list) ?
IMA_STATE_BIOS : IMA_STATE_INIT;
@ -393,8 +356,16 @@ METHOD(pts_component_t, measure, status_t,
switch (this->state)
{
case IMA_STATE_INIT:
/* disable padding for SHA1 legacy hash */
if (pcr_algo == PTS_MEAS_ALGO_SHA1)
{
this->pcr_padding = FALSE;
}
this->ima_list = pts_ima_event_list_create(
IMA_RUNTIME_MEASUREMENTS);
IMA_RUNTIME_MEASUREMENTS,
pcr_algo, this->pcr_padding);
if (!this->ima_list)
{
return FAILED;
@ -414,13 +385,17 @@ METHOD(pts_component_t, measure, status_t,
}
if (this->state == IMA_STATE_BOOT_AGGREGATE && this->bios_count)
{
if (!check_boot_aggregate(pcrs, measurement, algo))
boot_aggregate = chunk_create(pcr_buffer, pcr_size);
if (!check_boot_aggregate(pcrs, algo, this->pcr_padding,
boot_aggregate, measurement))
{
return FAILED;
}
}
evid = extend_pcr(this, qualifier, pcrs, IMA_PCR,
measurement);
evid = extend_pcr(this, qualifier, pcrs, IMA_PCR, measurement,
this->pcr_padding ? PTS_PCR_TRANSFORM_SHORT :
PTS_PCR_TRANSFORM_MATCH);
if (evid)
{
if (algo)
@ -528,7 +503,8 @@ METHOD(pts_component_t, verify, status_t,
{
bool has_pcr_info;
uint32_t pcr;
pts_meas_algorithms_t algo;
size_t pcr_size;
pts_meas_algorithms_t algo, pcr_algo;
pts_pcr_transform_t transform;
pts_pcr_t *pcrs;
time_t creation_time;
@ -536,13 +512,25 @@ METHOD(pts_component_t, verify, status_t,
status_t status = NOT_FOUND;
this->aik_id = pts->get_aik_id(pts);
pcrs = pts->get_pcrs(pts);
if (!pcrs)
{
return FAILED;
}
pcr_algo = pcrs->get_pcr_algo(pcrs);
pcr_size = pts_meas_algo_hash_size(pcr_algo);
measurement = evidence->get_measurement(evidence, &pcr, &algo, &transform,
&creation_time);
if (algo != pcr_algo)
{
DBG1(DBG_PTS, "received %N measurement hash but PCR bank is %N",
pts_meas_algorithm_names, algo, pts_meas_algorithm_names, algo);
return FAILED;
}
this->pcr_padding = (transform == PTS_PCR_TRANSFORM_SHORT);
if (qualifier == (PTS_ITA_QUALIFIER_FLAG_KERNEL |
PTS_ITA_QUALIFIER_TYPE_TRUSTED))
@ -606,6 +594,8 @@ METHOD(pts_component_t, verify, status_t,
int ima_count;
char *ima_algo, *ima_name;
char algo_buf[IMA_ALGO_LEN_MAX];
uint8_t pcr_buffer[HASH_SIZE_SHA512];
chunk_t boot_aggregate;
pts_meas_algorithms_t hash_algo;
hash_algo = parse_validation_uri(evidence, &ima_name, &ima_algo,
@ -622,15 +612,17 @@ METHOD(pts_component_t, verify, status_t,
"but is '%s'", ima_name);
return FAILED;
}
if (hash_algo != PTS_MEAS_ALGO_SHA1)
if (hash_algo != pcr_algo)
{
DBG1(DBG_PTS, "ima: boot_aggregate algorithm must be %N "
"but is %N",
pts_meas_algorithm_names, PTS_MEAS_ALGO_SHA1,
pts_meas_algorithm_names, pcr_algo,
pts_meas_algorithm_names, hash_algo);
return FAILED;
}
if (!check_boot_aggregate(pcrs, measurement, ima_algo))
boot_aggregate = chunk_create(pcr_buffer, pcr_size);
if (!check_boot_aggregate(pcrs, ima_algo, this->pcr_padding,
boot_aggregate, measurement))
{
return FAILED;
}
@ -652,8 +644,8 @@ METHOD(pts_component_t, verify, status_t,
DBG1(DBG_PTS, "checking boot aggregate evidence "
"measurement");
status = this->pts_db->check_comp_measurement(this->pts_db,
measurement, this->ima_cid,
this->aik_id, 1, pcr, algo);
boot_aggregate, this->ima_cid,
this->aik_id, 1, pcr, algo);
}
else
{
@ -661,8 +653,8 @@ METHOD(pts_component_t, verify, status_t,
"measurement");
this->is_ima_registering = TRUE;
status = this->pts_db->insert_comp_measurement(this->pts_db,
measurement, this->ima_cid,
this->aik_id, 1, pcr, algo);
boot_aggregate, this->ima_cid,
this->aik_id, 1, pcr, algo);
}
this->state = IMA_STATE_RUNTIME;
@ -673,20 +665,33 @@ METHOD(pts_component_t, verify, status_t,
break;
case IMA_STATE_RUNTIME:
{
uint8_t hash_buf[HASH_SIZE_SHA512];
uint8_t digest_buf[HASH_SIZE_SHA512], *hex_digest_buf;
chunk_t hex_digest, digest, hash;
uint8_t digest_buf[HASH_SIZE_SHA512], *hex_digest_buf;
uint8_t hash_buf[HASH_SIZE_SHA512];
size_t hash_size;
pts_meas_algorithms_t meas_algo;
enumerator_t *e;
this->count++;
if (evidence->get_validation(evidence, NULL) !=
PTS_COMP_EVID_VALIDATION_PASSED)
PTS_COMP_EVID_VALIDATION_PASSED)
{
DBG1(DBG_PTS, "evidence validation failed");
this->count_failed++;
return FAILED;
}
hash = chunk_create(hash_buf, pts_meas_algo_hash_size(algo));
hash_size = pts_meas_algo_hash_size(algo);
hash = chunk_create(hash_buf, hash_size);
if (this->pcr_padding)
{
memset(hash_buf, 0x00, hash_size);
meas_algo = PTS_MEAS_ALGO_SHA1;
}
else
{
meas_algo = algo;
}
e = this->pts_db->create_file_meas_enumerator(this->pts_db,
pts->get_platform_id(pts),
@ -697,8 +702,9 @@ METHOD(pts_component_t, verify, status_t,
{
hex_digest = chunk_from_str(hex_digest_buf);
digest = chunk_from_hex(hex_digest, digest_buf);
if (!ima_hash(digest, ima_algo, ima_name,
FALSE, algo, hash_buf))
if (!pts_ima_event_hash(digest, ima_algo, ima_name,
meas_algo, hash_buf))
{
status = FAILED;
break;
@ -916,6 +922,8 @@ pts_component_t *pts_ita_comp_ima_create(uint32_t depth,
.pts_db = pts_db,
.pcr_info = lib->settings->get_bool(lib->settings,
"%s.plugins.imc-attestation.pcr_info", FALSE, lib->ns),
.pcr_padding = lib->settings->get_bool(lib->settings,
"%s.plugins.imc-attestation.pcr_padding", FALSE, lib->ns),
.ref = 1,
);

View File

@ -31,6 +31,9 @@ typedef struct event_entry_t event_entry_t;
#define IMA_NG_TYPE_LEN 6
#define IMA_TYPE_LEN_MAX 10
#define IMA_ALGO_DIGEST_LEN_MAX IMA_ALGO_LEN_MAX + HASH_SIZE_SHA512
#define IMA_FILENAME_LEN_MAX 255
/**
* Private data of a pts_ima_event_list_t object.
@ -61,7 +64,7 @@ struct private_pts_ima_event_list_t {
struct event_entry_t {
/**
* SHA1 measurement hash
* Special IMA measurement hash
*/
chunk_t measurement;
@ -125,11 +128,14 @@ METHOD(pts_ima_event_list_t, destroy, void,
/**
* See header
*/
pts_ima_event_list_t* pts_ima_event_list_create(char *file)
pts_ima_event_list_t* pts_ima_event_list_create(char *file,
pts_meas_algorithms_t pcr_algo, bool pcr_padding)
{
private_pts_ima_event_list_t *this;
event_entry_t *entry;
chunk_t digest;
uint32_t pcr, type_len, name_len, eventdata_len, algo_digest_len, algo_len;
size_t hash_size;
char type[IMA_TYPE_LEN_MAX];
char algo_digest[IMA_ALGO_DIGEST_LEN_MAX];
char *pos, *error = "";
@ -164,6 +170,8 @@ pts_ima_event_list_t* pts_ima_event_list_create(char *file)
.list = linked_list_create(),
);
hash_size = pts_meas_algo_hash_size(pcr_algo);
while (TRUE)
{
/* read 32 bit PCR number in host order */
@ -175,12 +183,13 @@ pts_ima_event_list_t* pts_ima_event_list_create(char *file)
DBG2(DBG_PTS, "loaded ima measurements '%s' (%d entries)",
file, this->list->get_count(this->list));
close(fd);
return &this->public;
}
/* create and initialize new IMA entry */
entry = malloc_thing(event_entry_t);
entry->measurement = chunk_alloc(HASH_SIZE_SHA1);
entry->measurement = chunk_alloc(hash_size);
entry->algo = NULL;
entry->name = NULL;
@ -190,7 +199,12 @@ pts_ima_event_list_t* pts_ima_event_list_create(char *file)
break;
}
/* read 20 byte SHA-1 measurement digest */
if (pcr_padding)
{
memset(entry->measurement.ptr, 0x00, hash_size);
}
/* read 20 byte SHA-1 IMA measurement digest */
if (read(fd, entry->measurement.ptr, HASH_SIZE_SHA1) != HASH_SIZE_SHA1)
{
error = "invalid SHA-1 digest field";
@ -271,6 +285,9 @@ pts_ima_event_list_t* pts_ima_event_list_create(char *file)
entry->algo = malloc(algo_len);
memcpy(entry->algo, algo_digest, algo_len);
/* extract the digest */
digest = chunk_create(pos + 1, algo_digest_len - algo_len);
/* read the 32 bit length of the event name in host order */
if (read(fd, &name_len, 4) != 4 ||
eventdata_len != 4 + algo_digest_len + 4 + name_len)
@ -288,6 +305,17 @@ pts_ima_event_list_t* pts_ima_event_list_create(char *file)
error = "invalid filename field";
break;
}
/* re-compute IMA measurement digest for non-SHA1 hash algorithms */
if (pcr_algo != PTS_MEAS_ALGO_SHA1 && !pcr_padding)
{
if (!pts_ima_event_hash(digest, entry->algo, entry->name,
pcr_algo, entry->measurement.ptr))
{
break;
}
}
}
else
{
@ -328,3 +356,61 @@ pts_ima_event_list_t* pts_ima_event_list_create(char *file)
return NULL;
}
/**
* See header
*/
bool pts_ima_event_hash(chunk_t digest, char *ima_algo, char *ima_name,
pts_meas_algorithms_t pcr_algo, char *hash_buf)
{
hash_algorithm_t hash_alg;
hasher_t *hasher;
bool success;
hash_alg = pts_meas_algo_to_hash(pcr_algo);
hasher = lib->crypto->create_hasher(lib->crypto, hash_alg);
if (!hasher)
{
DBG1(DBG_PTS, "%N hasher could not be created",
hash_algorithm_short_names, hash_alg);
return FALSE;
}
if (ima_algo)
{
uint32_t ad_len, n_len;
chunk_t algo_name, event_name, algo_digest_len, name_len;
/* IMA-NG hash */
algo_name = chunk_create(ima_algo, strlen(ima_algo) + 1);
event_name = chunk_create(ima_name, strlen(ima_name) + 1);
ad_len = htole32(algo_name.len + digest.len);
algo_digest_len = chunk_create((uint8_t*)&ad_len, sizeof(ad_len));
n_len = htole32(event_name.len);
name_len = chunk_create((uint8_t*)&n_len, sizeof(n_len));
success = hasher->get_hash(hasher, algo_digest_len, NULL) &&
hasher->get_hash(hasher, algo_name, NULL) &&
hasher->get_hash(hasher, digest, NULL) &&
hasher->get_hash(hasher, name_len, NULL) &&
hasher->get_hash(hasher, event_name, hash_buf);
}
else
{
u_char filename_buffer[IMA_FILENAME_LEN_MAX + 1];
chunk_t file_name;
/* IMA legacy hash */
memset(filename_buffer, 0, sizeof(filename_buffer));
strncpy(filename_buffer, ima_name, IMA_FILENAME_LEN_MAX);
file_name = chunk_create (filename_buffer, sizeof(filename_buffer));
success = hasher->get_hash(hasher, digest, NULL) &&
hasher->get_hash(hasher, file_name, hash_buf);
}
hasher->destroy(hasher);
return success;
}

View File

@ -21,6 +21,8 @@
#ifndef PTS_IMA_EVENT_LIST_H_
#define PTS_IMA_EVENT_LIST_H_
#include "pts_meas_algo.h"
#include <time.h>
#include <library.h>
@ -56,7 +58,7 @@ struct pts_ima_event_list_t {
* Get the next file measurement and remove it from the list
*
* @param measurement Measurement hash
* @param algo Algorithm used to hash files
* @param algo Algorithm used to compute file digests
" @param name Event name (absolute filename or boot_aggregate)
* @return Return code
*/
@ -74,7 +76,23 @@ struct pts_ima_event_list_t {
* Create a PTS IMA runtime file measurement object
*
* @param file Pathname pointing to the IMA runtime measurements
* @param pcr_algo PCR hash measurement algorithm to be used
* @param pcr_padding Apply PCR hash padding if hash algorithm is lacking
*/
pts_ima_event_list_t* pts_ima_event_list_create(char *file);
pts_ima_event_list_t* pts_ima_event_list_create(char *file,
pts_meas_algorithms_t pcr_algo, bool pcr_padding);
/**
* Generate an IMA or IMA-NG hash from an event digest and event name
*
* @param digest event digest
* @param ima_algo event digest algorithm string ("sha1:", "sha256:", etc.)
* @param ima_name event name
* @param pcr_algo hash algorithm used by TPM PCR extension
* @param hash_buf hash value to be compared with TPM measurement
* @return TRUE if computation successful
*/
bool pts_ima_event_hash(chunk_t digest, char *ima_algo, char *ima_name,
pts_meas_algorithms_t pcr_algo, char *hash_buf);
#endif /** PTS_IMA_EVENT_LIST_H_ @}*/