" 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"
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;
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=.");
"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 */
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)
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);
_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");
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;
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 },
{}
};
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
#