]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
measure: add policy-digest verb
authorLuca Boccassi <luca.boccassi@gmail.com>
Sun, 19 Jan 2025 22:50:53 +0000 (22:50 +0000)
committerLuca Boccassi <luca.boccassi@gmail.com>
Tue, 21 Jan 2025 00:07:58 +0000 (00:07 +0000)
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.

man/systemd-measure.xml
src/measure/measure.c
test/units/TEST-70-TPM2.measure.sh

index 97f1abe1f9f8ee4e63096a501f52dfc08fa21e80..77a293ac2e1a24373e86481798ab077eb54f04fd 100644 (file)
 
         <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>
 
index fceca7c4a113632985680015c511fde8c55084f9..dcae8528e073efa54f47357ab327a596eff70937 100644 (file)
@@ -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"
@@ -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;
@@ -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=.");
 
@@ -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 */
@@ -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)
@@ -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);
@@ -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");
 
@@ -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;
@@ -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          },
                 {}
         };
 
index 3336f383305cd0c635380c239d41fa882276ed5e..bf30bd57b3340dcfcdd31184290fb930c589672d 100755 (executable)
@@ -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
 #