mirror of
https://github.com/ibm-s390-linux/s390-tools.git
synced 2026-08-05 02:14:52 +00:00
a4e396fdd7
In verbose mode (-v) `pvattest verify´ now also prints "Attestation measurement verified", if the verification was successful and the Config UID of the SE-guest and any additional data if available. $ pvattest -V verify [...] Attestation measurement verified Config UID: 1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a Additional Data: 1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b 1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b1b Signed-off-by: Steffen Eiden <seiden@linux.ibm.com> Reviewed-by: Marc Hartmayer <mhartmay@linux.ibm.com> Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
402 lines
12 KiB
C
402 lines
12 KiB
C
/*
|
|
* Entry point for the pvattest tool.
|
|
*
|
|
* Copyright IBM Corp. 2022
|
|
*
|
|
* s390-tools is free software; you can redistribute it and/or modify
|
|
* it under the terms of the MIT license. See LICENSE for details.
|
|
*/
|
|
/* Must be included before any other header */
|
|
#include "config.h"
|
|
|
|
#include <stdio.h>
|
|
#include <unistd.h>
|
|
|
|
#include <openssl/evp.h>
|
|
|
|
#include "libpv/crypto.h"
|
|
#include "libpv/cert.h"
|
|
|
|
#include "uvio.h"
|
|
#include "common.h"
|
|
#include "attestation.h"
|
|
#include "arcb.h"
|
|
#include "argparse.h"
|
|
#include "exchange_format.h"
|
|
#include "log.h"
|
|
|
|
#define PVATTEST_NID NID_secp521r1
|
|
#define PVATTEST_UV_PATH "/dev/uv"
|
|
#define PVATTEST_EXIT_MEASURE_NOT_VERIFIED 2
|
|
|
|
enum pvattest_error {
|
|
PVATTEST_ERROR_INVAL_ATT_RESULT,
|
|
};
|
|
|
|
static arcb_v1_t *create_arcb(char **host_key_paths, const gboolean use_nonce,
|
|
const gboolean phkh_img, const gboolean phkh_att,
|
|
const uint64_t user_paf, GError **error)
|
|
{
|
|
g_autoptr(GBytes) arpk = NULL, meas_key = NULL, nonce = NULL, iv = NULL;
|
|
g_autoslist(PvX509WithPath) host_keys_with_path = NULL;
|
|
g_autoslist(EVP_PKEY) evp_host_keys = NULL;
|
|
const uint32_t mai = MAI_HMAC_SHA512;
|
|
g_autoptr(EVP_PKEY) evp_cpk = NULL;
|
|
g_autoptr(arcb_v1_t) arcb = NULL;
|
|
uint64_t paf = user_paf;
|
|
|
|
g_assert(host_key_paths);
|
|
|
|
arpk = pv_generate_key(EVP_aes_256_gcm(), error);
|
|
if (!arpk)
|
|
return NULL;
|
|
iv = pv_generate_iv(EVP_aes_256_gcm(), error);
|
|
if (!iv)
|
|
return NULL;
|
|
evp_cpk = pv_generate_ec_key(PVATTEST_NID, error);
|
|
if (!evp_cpk)
|
|
return NULL;
|
|
meas_key = pv_generate_rand_data(HMAC_SHA512_KEY_SIZE, error);
|
|
if (!meas_key)
|
|
return NULL;
|
|
|
|
if (phkh_img)
|
|
paf |= ARCB_V1_PAF_AAD_PHKH_HEADER;
|
|
if (phkh_att)
|
|
paf |= ARCB_V1_PAF_AAD_PHKH_ATTEST;
|
|
|
|
arcb = arcb_v1_new(arpk, iv, mai, evp_cpk, meas_key, paf, error);
|
|
if (!arcb)
|
|
return NULL;
|
|
if (use_nonce) {
|
|
nonce = pv_generate_rand_data(ARCB_V1_NONCE_SIZE, error);
|
|
if (!nonce)
|
|
return NULL;
|
|
arcb_v1_set_nonce(arcb, nonce);
|
|
}
|
|
|
|
host_keys_with_path = pv_load_certificates(host_key_paths, error);
|
|
if (!host_keys_with_path)
|
|
return NULL;
|
|
|
|
/* Extract EVP_PKEY structures and verify that the correct elliptic
|
|
* curve is used.
|
|
*/
|
|
evp_host_keys = pv_get_ec_pubkeys(host_keys_with_path, PVATTEST_NID, error);
|
|
if (!evp_host_keys)
|
|
return NULL;
|
|
for (GSList *iter = evp_host_keys; iter; iter = iter->next) {
|
|
EVP_PKEY *host_key = iter->data;
|
|
|
|
if (arcb_v1_add_key_slot(arcb, host_key, error) < 0)
|
|
return NULL;
|
|
}
|
|
return g_steal_pointer(&arcb);
|
|
}
|
|
|
|
#define __PVATTEST_CREATE_ERROR_MSG _("Creating the attestation request failed")
|
|
static int do_create(const pvattest_create_config_t *create_config)
|
|
{
|
|
g_autoptr(exchange_format_ctx_t) output_ctx = NULL;
|
|
uint32_t measurement_size, additional_data_size;
|
|
g_autoptr(GBytes) serialized_arcb = NULL;
|
|
g_autoptr(arcb_v1_t) arcb = NULL;
|
|
g_autoptr(GError) error = NULL;
|
|
g_autoptr(GBytes) arpk = NULL;
|
|
|
|
if (!create_config->use_nonce)
|
|
pvattest_log_warning(_("No nonce used. (Experimental setting)"));
|
|
|
|
if (create_config->no_verify) {
|
|
pvattest_log_warning(_("Host-key document verification is disabled.\n"
|
|
"The attestation result could be compromised!"));
|
|
pvattest_log_debug(_("Verification skipped."));
|
|
} else {
|
|
if (pv_verify_host_key_docs_by_path(
|
|
create_config->host_key_document_paths, create_config->root_ca_path,
|
|
create_config->crl_paths, create_config->certificate_paths,
|
|
create_config->online, &error) < 0)
|
|
goto err_exit;
|
|
pvattest_log_debug(_("Verification passed."));
|
|
}
|
|
|
|
/* build attestation request */
|
|
arcb = create_arcb(create_config->host_key_document_paths, create_config->use_nonce,
|
|
create_config->phkh_img, create_config->phkh_att, create_config->paf,
|
|
&error);
|
|
if (!arcb)
|
|
goto err_exit;
|
|
|
|
additional_data_size = arcb_v1_get_required_additional_size(arcb);
|
|
if (create_config->x_aad_size >= 0) {
|
|
g_assert_cmpint(create_config->x_aad_size, <=, UINT32_MAX);
|
|
additional_data_size = (uint32_t)create_config->x_aad_size;
|
|
}
|
|
measurement_size = arcb_v1_get_required_measurement_size(arcb, &error);
|
|
if (error)
|
|
goto err_exit;
|
|
|
|
serialized_arcb = arcb_v1_serialize(arcb, &error);
|
|
if (!serialized_arcb)
|
|
goto err_exit;
|
|
|
|
/* write attestation request data to file */
|
|
output_ctx = exchange_ctx_new(PVATTEST_EXCHANGE_VERSION_1_00, serialized_arcb,
|
|
measurement_size, additional_data_size, &error);
|
|
if (!output_ctx)
|
|
goto err_exit;
|
|
if (exchange_write_to_file(output_ctx, create_config->output_path, &error) < 0)
|
|
goto err_exit;
|
|
pvattest_log_debug(_("ARCB written to file."));
|
|
|
|
/* write attestation request protection key to file */
|
|
arpk = arcb_v1_get_arp_key(arcb);
|
|
wrapped_g_file_set_content(create_config->arp_key_out_path, arpk, 0600, &error);
|
|
if (error)
|
|
goto err_exit;
|
|
pvattest_log_debug(_("ARPK written to file."));
|
|
|
|
return EXIT_SUCCESS;
|
|
|
|
err_exit:
|
|
pvattest_log_GError(__PVATTEST_CREATE_ERROR_MSG, error);
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
#ifdef PVATTEST_COMPILE_PERFORM
|
|
#define __PVATTEST_MEASURE_ERROR_MSG _("Performing the attestation measurement failed")
|
|
static int do_perform(pvattest_perform_config_t *perform_config)
|
|
{
|
|
g_autoptr(GBytes) serialized_arcb = NULL, user_data = NULL, measurement = NULL,
|
|
additional_data = NULL, config_uid = NULL;
|
|
size_t uv_measurement_data_size, uv_addidtional_data_size;
|
|
g_autoptr(exchange_format_ctx_t) exchange_ctx = NULL;
|
|
uint32_t measurement_size, additional_data_size;
|
|
g_autoptr(uvio_attest_t) uvio_attest = NULL;
|
|
g_autoptr(GError) error = NULL;
|
|
be16_t uv_rc;
|
|
int uv_fd;
|
|
|
|
exchange_ctx = exchange_ctx_from_file(perform_config->input_path, &error);
|
|
if (!exchange_ctx)
|
|
goto err_exit;
|
|
|
|
serialized_arcb = exchange_get_serialized_arcb(exchange_ctx);
|
|
if (!serialized_arcb) {
|
|
g_set_error(&error, PVATTEST_ERROR, ARCB_ERR_INVALID_ARCB,
|
|
_("The input does not provide an attestation request."));
|
|
|
|
goto err_exit;
|
|
}
|
|
|
|
measurement_size = exchange_get_requested_measurement_size(exchange_ctx);
|
|
additional_data_size = exchange_get_requested_additional_data_size(exchange_ctx);
|
|
|
|
pvattest_log_debug(_("Input data loaded."));
|
|
|
|
if (perform_config->user_data_path) {
|
|
user_data = pv_file_get_content_as_g_bytes(perform_config->user_data_path, &error);
|
|
if (!user_data)
|
|
goto err_exit;
|
|
pvattest_log_debug(_("Added user data from '%s'"), perform_config->user_data_path);
|
|
}
|
|
uvio_attest = build_attestation_v1_ioctl(serialized_arcb, user_data, measurement_size,
|
|
additional_data_size, &error);
|
|
if (!uvio_attest)
|
|
goto err_exit;
|
|
|
|
pvattest_log_debug(_("attestation context generated."));
|
|
|
|
/* execute attestation */
|
|
uv_fd = uvio_open(PVATTEST_UV_PATH, &error);
|
|
if (uv_fd < 0)
|
|
goto err_exit;
|
|
|
|
uv_rc = uvio_ioctl_attest(uv_fd, uvio_attest, &error);
|
|
close(uv_fd);
|
|
if (uv_rc != UVC_EXECUTED)
|
|
goto err_exit;
|
|
pvattest_log_debug(_("attestation measurement successful. rc = %#x"), uv_rc);
|
|
|
|
/* write to file */
|
|
measurement = uvio_get_measurement(uvio_attest);
|
|
additional_data = uvio_get_additional_data(uvio_attest);
|
|
config_uid = uvio_get_config_uid(uvio_attest);
|
|
|
|
uv_measurement_data_size = measurement == NULL ? 0 : g_bytes_get_size(measurement);
|
|
if (uv_measurement_data_size != measurement_size) {
|
|
g_set_error(&error, PVATTEST_ERROR, PVATTEST_ERROR_INVAL_ATT_RESULT,
|
|
"The measurement size returned by Ultravisor is not as expected.");
|
|
goto err_exit;
|
|
}
|
|
|
|
uv_addidtional_data_size = additional_data == NULL ? 0 : g_bytes_get_size(additional_data);
|
|
if (uv_addidtional_data_size != additional_data_size) {
|
|
g_set_error(&error, PVATTEST_ERROR, PVATTEST_ERROR_INVAL_ATT_RESULT,
|
|
"The additional data size returned by Ultravisor is not as expected.");
|
|
goto err_exit;
|
|
}
|
|
|
|
exchange_set_measurement(exchange_ctx, measurement);
|
|
if (additional_data)
|
|
exchange_set_additional_data(exchange_ctx, additional_data);
|
|
exchange_set_config_uid(exchange_ctx, config_uid);
|
|
if (user_data)
|
|
exchange_set_user_data(exchange_ctx, user_data);
|
|
|
|
if (exchange_write_to_file(exchange_ctx, perform_config->output_path, &error) < 0)
|
|
goto err_exit;
|
|
|
|
pvattest_log_debug(_("Output written to file."));
|
|
|
|
return EXIT_SUCCESS;
|
|
|
|
err_exit:
|
|
pvattest_log_GError(__PVATTEST_MEASURE_ERROR_MSG, error);
|
|
return EXIT_FAILURE;
|
|
}
|
|
#endif /* PVATTEST_COMPILE_PERFORM */
|
|
|
|
#define __PVATTEST_VERIFY_ERROR_MSG _("Attestation measurement verification failed")
|
|
static int do_verify(pvattest_verify_config_t *verify_config)
|
|
{
|
|
g_autoptr(GBytes) user_data = NULL, uv_measurement = NULL, additional_data = NULL,
|
|
image_hdr = NULL, calc_measurement = NULL, config_uid = NULL,
|
|
meas_key = NULL, arp_key = NULL, nonce = NULL, serialized_arcb = NULL;
|
|
g_autofree att_meas_ctx_t *measurement_hdr = NULL;
|
|
g_autoptr(exchange_format_ctx_t) input_ctx = NULL;
|
|
g_autoptr(GError) error = NULL;
|
|
gboolean rc;
|
|
|
|
image_hdr = pv_file_get_content_as_g_bytes(verify_config->hdr_path, &error);
|
|
if (!image_hdr)
|
|
goto err_exit;
|
|
|
|
measurement_hdr = att_extract_from_hdr(image_hdr, &error);
|
|
if (!measurement_hdr)
|
|
goto err_exit;
|
|
|
|
pvattest_log_debug(_("Image header loaded."));
|
|
|
|
input_ctx = exchange_ctx_from_file(verify_config->input_path, &error);
|
|
if (!input_ctx)
|
|
goto err_exit;
|
|
|
|
config_uid = exchange_get_config_uid(input_ctx);
|
|
uv_measurement = exchange_get_measurement(input_ctx);
|
|
user_data = exchange_get_user_data(input_ctx);
|
|
additional_data = exchange_get_additional_data(input_ctx);
|
|
serialized_arcb = exchange_get_serialized_arcb(input_ctx);
|
|
|
|
if (!uv_measurement || !serialized_arcb) {
|
|
g_set_error(&error, PVATTEST_ERROR, PVATTEST_SUBC_INVALID,
|
|
_("Input data has no measurement"));
|
|
goto err_exit;
|
|
}
|
|
pvattest_log_debug(_("Input data loaded."));
|
|
|
|
att_add_uid(measurement_hdr, config_uid);
|
|
|
|
arp_key = pv_file_get_content_as_g_bytes(verify_config->arp_key_in_path, &error);
|
|
if (!arp_key)
|
|
goto err_exit;
|
|
pvattest_log_debug(_("ARPK loaded."));
|
|
|
|
rc = arcb_v1_verify_serialized_arcb(serialized_arcb, arp_key, &meas_key, &nonce, &error);
|
|
if (!rc)
|
|
goto err_exit;
|
|
|
|
pvattest_log_debug(_("Input ARCB verified."));
|
|
|
|
calc_measurement = att_gen_measurement_hmac_sha512(measurement_hdr, meas_key, user_data,
|
|
nonce, additional_data, &error);
|
|
if (!calc_measurement)
|
|
goto err_exit;
|
|
pvattest_log_debug(_("Measurement calculated."));
|
|
|
|
if (!att_verify_measurement(calc_measurement, uv_measurement, &error)) {
|
|
pvattest_log_GError(__PVATTEST_VERIFY_ERROR_MSG, error);
|
|
pvattest_log_debug(_("Measurement values:"));
|
|
gbhexdump(uv_measurement);
|
|
gbhexdump(calc_measurement);
|
|
return PVATTEST_EXIT_MEASURE_NOT_VERIFIED;
|
|
}
|
|
|
|
pvattest_log_info(_("Attestation measurement verified"));
|
|
pvattest_log_info(_("Config UID:"));
|
|
pvattest_log_bytes(g_bytes_get_data(config_uid, NULL), g_bytes_get_size(config_uid), 16L,
|
|
"", FALSE, PVATTEST_LOG_LVL_INFO);
|
|
|
|
if (additional_data) {
|
|
pvattest_log_info(_("\nAdditional Data:"));
|
|
pvattest_log_bytes(g_bytes_get_data(additional_data, NULL),
|
|
g_bytes_get_size(additional_data), 16L, "", FALSE,
|
|
PVATTEST_LOG_LVL_INFO);
|
|
}
|
|
return EXIT_SUCCESS;
|
|
|
|
err_exit:
|
|
pvattest_log_GError(__PVATTEST_VERIFY_ERROR_MSG, error);
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
/*
|
|
* Will not free the config structs, but the nested char* etc.
|
|
* that's what we need to do as we will receive a statically allocated config_t
|
|
* Not defined in the parse header as someone might incorrectly assume
|
|
* that the config pointers will be freed.
|
|
*/
|
|
WRAPPED_G_DEFINE_AUTOPTR_CLEANUP_FUNC(pvattest_config_t, pvattest_parse_clear_config)
|
|
int main(int argc, char *argv[])
|
|
{
|
|
int appl_log_lvl = PVATTEST_LOG_LVL_DEFAULT;
|
|
g_autoptr(pvattest_config_t) config = NULL;
|
|
g_autoptr(GError) error = NULL;
|
|
enum pvattest_command command;
|
|
int rc;
|
|
|
|
/* setting up the default log handler to filter messages based on the
|
|
* log level specified by the user.
|
|
*/
|
|
g_log_set_handler(NULL, G_LOG_LEVEL_MASK | G_LOG_FLAG_FATAL | G_LOG_FLAG_RECURSION,
|
|
&pvattest_log_default_logger, &appl_log_lvl);
|
|
/* setting up the log handler for hexdumps (no prefix and '\n' at end of
|
|
* message)to filter messages based on the log level specified by the
|
|
* user.
|
|
*/
|
|
g_log_set_handler(PVATTEST_BYTES_LOG_DOMAIN,
|
|
G_LOG_LEVEL_MASK | G_LOG_FLAG_FATAL | G_LOG_FLAG_RECURSION,
|
|
&pvattest_log_plain_logger, &appl_log_lvl);
|
|
|
|
command = pvattest_parse(&argc, &argv, &config, &error);
|
|
if (command == PVATTEST_SUBC_INVALID) {
|
|
pvattest_log_error(_("%s\nTry '%s --help' for more information"), error->message,
|
|
GETTEXT_PACKAGE);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
g_assert(config);
|
|
appl_log_lvl = config->general.log_level;
|
|
|
|
pv_init();
|
|
|
|
switch (command) {
|
|
case PVATTEST_SUBC_CREATE:
|
|
rc = do_create(&config->create);
|
|
break;
|
|
#ifdef PVATTEST_COMPILE_PERFORM
|
|
case PVATTEST_SUBC_PERFORM:
|
|
rc = do_perform(&config->perform);
|
|
break;
|
|
#endif /* PVATTEST_COMPILE_PERFORM */
|
|
case PVATTEST_SUBC_VERIFY:
|
|
rc = do_verify(&config->verify);
|
|
break;
|
|
default:
|
|
g_return_val_if_reached(EXIT_FAILURE);
|
|
}
|
|
|
|
pv_cleanup();
|
|
|
|
return rc;
|
|
}
|