Skip to content

Commit

Permalink
measure: add policy-digest verb
Browse files Browse the repository at this point in the history
When doing offline signing we need to know the exact payload
to sign, and the 'calculate' verb doesn't really show that, it
shows the PCR values. But what we sign is the hash of the policy.
So add a new verb that outputs the json payload that goes in the
.pcrsig section, without the .sig object, so that we can take them
and give the .pol object to an offline and asynchronous signing
service, such as SUSE's Open Build Service, and then add the .sig
object to the json and attach it to a UKI.
  • Loading branch information
bluca committed Jan 21, 2025
1 parent 9bfc13e commit 175cb87
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 25 deletions.
14 changes: 14 additions & 0 deletions man/systemd-measure.xml
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,20 @@

<xi:include href="version-info.xml" xpointer="v252"/></listitem>
</varlistentry>

<varlistentry>
<term><command>policy-digest</command></term>

<listitem><para>As with the <command>sign</command> command, pre-calculate the expected value
seen in TPM2 PCR register 11 after boot-up of a unified kernel image. Then, compute the resulting
TPM2 policy and print its digest. This will write a JSON object to standard output that contains
the policy digests for all specified PCR banks (see the <option>--bank=</option> option below),
so that it may be signed offline, for the cases where the private key is not directly accessible.
If <option>--public-key=</option> or <option>--certificate=</option> are specified, the JSON object
will also contain the key fingerprint.</para>

<xi:include href="version-info.xml" xpointer="v258"/></listitem>
</varlistentry>
</variablelist>
</refsect1>

Expand Down
55 changes: 34 additions & 21 deletions src/measure/measure.c
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ static int help(int argc, char *argv[], void *userdata) {
" status Show current PCR values\n"
" calculate Calculate expected PCR values\n"
" sign Calculate and sign expected PCR values\n"
" policy-digest Calculate expected TPM2 policy digests\n"
"\n%3$sOptions:%4$s\n"
" -h --help Show this help\n"
" --version Print version\n"
Expand Down Expand Up @@ -826,7 +827,7 @@ static int verb_calculate(int argc, char *argv[], void *userdata) {
return 0;
}

static int verb_sign(int argc, char *argv[], void *userdata) {
static int build_policy_digest(bool sign) {
_cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
_cleanup_(pcr_state_free_all) PcrState *pcr_states = NULL;
_cleanup_(openssl_ask_password_ui_freep) OpenSSLAskPasswordUI *ui = NULL;
Expand All @@ -839,7 +840,7 @@ static int verb_sign(int argc, char *argv[], void *userdata) {
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Either --linux= or --current must be specified, refusing.");

if (!arg_private_key)
if (sign && !arg_private_key)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"No private key specified, use --private-key=.");

Expand All @@ -856,7 +857,7 @@ static int verb_sign(int argc, char *argv[], void *userdata) {
"File '%s' is not a valid JSON object, refusing.", arg_append);
}

/* When signing we only support JSON output */
/* When signing/building digest we only support JSON output */
arg_json_format_flags &= ~SD_JSON_FORMAT_OFF;

/* This must be done before openssl_load_private_key() otherwise it will get stuck */
Expand Down Expand Up @@ -918,11 +919,11 @@ static int verb_sign(int argc, char *argv[], void *userdata) {
SYNTHETIC_ERRNO(EIO),
"Failed to extract public key from certificate %s.",
arg_certificate);
} else {
} else if (sign) {
_cleanup_(memstream_done) MemStream m = {};
FILE *tf;

/* No public key was specified, let's derive it automatically, if we can */
/* No public key was specified, let's derive it automatically, if we can, when signing */

tf = memstream_init(&m);
if (!tf)
Expand Down Expand Up @@ -978,17 +979,20 @@ static int verb_sign(int argc, char *argv[], void *userdata) {
return log_error_errno(r, "Could not calculate PolicyPCR digest: %m");

_cleanup_free_ void *sig = NULL;
size_t ss;

r = digest_and_sign(p->md, privkey, pcr_policy_digest.buffer, pcr_policy_digest.size, &sig, &ss);
if (r < 0)
return log_error_errno(r, "Failed to sign PCR policy: %m");
size_t ss = 0;
if (privkey) {
r = digest_and_sign(p->md, privkey, pcr_policy_digest.buffer, pcr_policy_digest.size, &sig, &ss);
if (r < 0)
return log_error_errno(r, "Failed to sign PCR policy: %m");
}

_cleanup_free_ void *pubkey_fp = NULL;
size_t pubkey_fp_size = 0;
r = pubkey_fingerprint(pubkey, EVP_sha256(), &pubkey_fp, &pubkey_fp_size);
if (r < 0)
return r;
if (pubkey) {
r = pubkey_fingerprint(pubkey, EVP_sha256(), &pubkey_fp, &pubkey_fp_size);
if (r < 0)
return r;
}

_cleanup_(sd_json_variant_unrefp) sd_json_variant *a = NULL;
r = tpm2_make_pcr_json_array(UINT64_C(1) << TPM2_PCR_KERNEL_BOOT, &a);
Expand All @@ -997,10 +1001,10 @@ static int verb_sign(int argc, char *argv[], void *userdata) {

_cleanup_(sd_json_variant_unrefp) sd_json_variant *bv = NULL;
r = sd_json_buildo(&bv,
SD_JSON_BUILD_PAIR("pcrs", SD_JSON_BUILD_VARIANT(a)), /* PCR mask */
SD_JSON_BUILD_PAIR("pkfp", SD_JSON_BUILD_HEX(pubkey_fp, pubkey_fp_size)), /* SHA256 fingerprint of public key (DER) used for the signature */
SD_JSON_BUILD_PAIR("pol", SD_JSON_BUILD_HEX(pcr_policy_digest.buffer, pcr_policy_digest.size)), /* TPM2 policy hash that is signed */
SD_JSON_BUILD_PAIR("sig", SD_JSON_BUILD_BASE64(sig, ss))); /* signature data */
SD_JSON_BUILD_PAIR("pcrs", SD_JSON_BUILD_VARIANT(a)), /* PCR mask */
SD_JSON_BUILD_PAIR_CONDITION(pubkey_fp_size > 0, "pkfp", SD_JSON_BUILD_HEX(pubkey_fp, pubkey_fp_size)), /* SHA256 fingerprint of public key (DER) used for the signature */
SD_JSON_BUILD_PAIR("pol", SD_JSON_BUILD_HEX(pcr_policy_digest.buffer, pcr_policy_digest.size)), /* TPM2 policy hash that is signed */
SD_JSON_BUILD_PAIR_CONDITION(ss > 0, "sig", SD_JSON_BUILD_BASE64(sig, ss))); /* signature data */
if (r < 0)
return log_error_errno(r, "Failed to build JSON object: %m");

Expand Down Expand Up @@ -1028,6 +1032,14 @@ static int verb_sign(int argc, char *argv[], void *userdata) {
return 0;
}

static int verb_sign(int argc, char *argv[], void *userdata) {
return build_policy_digest(/* sign= */ true);
}

static int verb_policy_digest(int argc, char *argv[], void *userdata) {
return build_policy_digest(/* sign= */ false);
}

static int compare_reported_pcr_nr(uint32_t pcr, const char *varname, const char *description) {
_cleanup_free_ char *s = NULL;
uint32_t v;
Expand Down Expand Up @@ -1179,10 +1191,11 @@ static int verb_status(int argc, char *argv[], void *userdata) {

static int measure_main(int argc, char *argv[]) {
static const Verb verbs[] = {
{ "help", VERB_ANY, VERB_ANY, 0, help },
{ "status", VERB_ANY, 1, VERB_DEFAULT, verb_status },
{ "calculate", VERB_ANY, 1, 0, verb_calculate },
{ "sign", VERB_ANY, 1, 0, verb_sign },
{ "help", VERB_ANY, VERB_ANY, 0, help },
{ "status", VERB_ANY, 1, VERB_DEFAULT, verb_status },
{ "calculate", VERB_ANY, 1, 0, verb_calculate },
{ "policy-digest", VERB_ANY, 1, 0, verb_policy_digest },
{ "sign", VERB_ANY, 1, 0, verb_sign },
{}
};

Expand Down
25 changes: 21 additions & 4 deletions test/units/TEST-70-TPM2.measure.sh
Original file line number Diff line number Diff line change
Expand Up @@ -47,15 +47,32 @@ EOF

rm /tmp/result /tmp/result.json

# Generate key pair
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out "/tmp/pcrsign-private.pem"
openssl rsa -pubout -in "/tmp/pcrsign-private.pem" -out "/tmp/pcrsign-public.pem"

# Verify that the offline signature obtained via policy-digests is the same as an online signature created
# with the same key by systemd-measure
digest="$("$SD_MEASURE" policy-digest --json=short --linux=/tmp/tpmdata1 --initrd=/tmp/tpmdata2 --bank=sha256 --public-key="/tmp/pcrsign-public.pem" --phase=:)"
signed_digest="$("$SD_MEASURE" sign --json=short --linux=/tmp/tpmdata1 --initrd=/tmp/tpmdata2 --bank=sha256 --private-key="/tmp/pcrsign-private.pem" --public-key="/tmp/pcrsign-public.pem" --phase=:)"
echo "$digest" | jq -r '.sha256 | to_entries[] | .value.pol' | while read -r pol; do
# Note: basenc before coreutils 9.5 refuses lowercase hex input strings
offline_sig="$(echo -n "$pol" | \
tr '[:lower:]' '[:upper:]' | \
basenc --base16 --decode | \
openssl dgst -sign /tmp/pcrsign-private.pem -sha256 | \
base64 -w0)"
online_sig="$(echo "$signed_digest" | jq -r --arg pol "$pol" '.sha256[] | select(.pol == $pol) | .sig')"
test -n "$offline_sig"
test -n "$online_sig"
test "$offline_sig" = "$online_sig"
done

if ! tpm_has_pcr sha1 11 || ! tpm_has_pcr sha256 11; then
echo "PCR sysfs files not found, skipping signed PCR policy tests"
exit 0
fi

# Generate key pair
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out "/tmp/pcrsign-private.pem"
openssl rsa -pubout -in "/tmp/pcrsign-private.pem" -out "/tmp/pcrsign-public.pem"

MEASURE_BANKS=("--bank=sha256")
# Check if SHA1 signatures are supported
#
Expand Down

0 comments on commit 175cb87

Please sign in to comment.