mirror of
https://github.com/systemd/systemd.git
synced 2026-11-06 05:17:24 +00:00
report-sign-tpm2: certify the SRK and report its public area
Secrets sealed to the SRK, e.g. via the --tpm2-device-key= switch of systemd-cryptenroll and systemd-repart, can only be unsealed by the TPM the SRK lives in, and sealing needs no access to that TPM. With the SRK certified by the attestation key, a verifier of the report hence has a public key it can trust to encrypt data for the reporting system specifically. Since TPM2_Clear replaces the SRK, a certification of the same SRK in a later report also shows that the TPM has not been cleared in between. Certify the SRK with each signing key, and provide its public area as TSS2 JSON, like the signing key's. Marshalled into a TPM2B_PUBLIC this is what --tpm2-device-key= consumes. If there is no SRK one is created, like systemd-cryptenroll does. Whatever sits at the SRK handle is certified. Judging its attributes is up to the verifier, and whether it can be authorized with a password session is left to TPM2_Certify. The one exception is noDA: an object without it with an auth value we don't know would count every failed attempt towards the TPM's dictionary attack lockout, and reports are requested repeatedly, so such an object is not certified at all. Apart from that we only make sure up front that we can calculate the name and encode the public area, so that a pre-existing SRK we cannot handle doesn't fail the whole report, and hand the vetted public area to the report generator, so that an object swapped in at the handle afterwards fails the name check rather than being certified. Failing to acquire the SRK is not fatal either, the report is still useful without the certification.
This commit is contained in:
1 parent
fdbc5a717e
commit
0cb06c715a
2 files changed
+108
-2
No files matched your search
@@ -59,6 +59,24 @@
|
||||
<constant>TPMS_NV_PUBLIC</constant> structure), and a JSON blob of authenticated data (the NvPCR name
|
||||
and priority) that is digested and supplied as the qualifying data to the certify command.</para></listitem>
|
||||
|
||||
<listitem><para>A certification of the TPM's storage root key (SRK), generated with
|
||||
<constant>TPM2_Certify</constant>. This contains the JSON encoded attestation and the corresponding
|
||||
signature. The component also includes the public area of the SRK, as a JSON encoded
|
||||
<constant>TPMT_PUBLIC</constant> structure. Marshalled into a <constant>TPM2B_PUBLIC</constant>
|
||||
structure, this is the format accepted by the <option>--tpm2-device-key=</option> switch of
|
||||
<citerefentry><refentrytitle>systemd-cryptenroll</refentrytitle><manvolnum>1</manvolnum></citerefentry>
|
||||
and <citerefentry><refentrytitle>systemd-repart</refentrytitle><manvolnum>8</manvolnum></citerefentry>.
|
||||
The certification proves to a verifier that the SRK resides in the same TPM as the attestation key,
|
||||
so that secrets sealed to it, sealing to which requires no access to the TPM, can only be unsealed by
|
||||
the reporting system. As <constant>TPM2_Clear</constant> replaces the SRK, a certification of the same
|
||||
SRK in a later report is also evidence that the TPM has not been cleared in between. If no SRK exists
|
||||
yet, one is created and persisted at the usual handle, which requires the authorization value of the
|
||||
TPM's owner hierarchy to be empty. If the SRK cannot be acquired, or the object at its handle cannot
|
||||
be authorized with an empty password, the report is generated without this component. The same applies
|
||||
if the object lacks the <constant>noDA</constant> attribute, as a failed authorization attempt would
|
||||
then count towards the TPM's dictionary attack protection. Checking that the attributes of the
|
||||
certified key are those of a storage key that cannot leave the TPM is up to the verifier.</para></listitem>
|
||||
|
||||
<listitem><para>One component per reported TPM property, generated with <constant>TPM2_GetCapability</constant>.
|
||||
Each of these contain the property and its value as a JSON encoded <constant>TPMS_TAGGED_PROPERTY</constant>
|
||||
structure, as well as a boolean indicating the value of the <constant>moreData</constant> parameter returned
|
||||
|
||||
@@ -757,7 +757,7 @@ static int build_report(sd_json_variant **v, const Tpm2Report *report, const str
|
||||
_cleanup_(sd_json_variant_unrefp) sd_json_variant *cv = NULL;
|
||||
FOREACH_ARRAY(c, report->components, report->n_components) {
|
||||
_cleanup_(sd_json_variant_unrefp) sd_json_variant *pcr_values = NULL, *nv_public = NULL, *tpm_prop = NULL,
|
||||
*auth_policy = NULL;
|
||||
*auth_policy = NULL, *key_public = NULL;
|
||||
const char *session_audit_hash_alg = NULL;
|
||||
|
||||
switch (c->type) {
|
||||
@@ -788,6 +788,16 @@ static int build_report(sd_json_variant **v, const Tpm2Report *report, const str
|
||||
return log_error_errno(r, "Cannot convert NvPCR public area to JSON: %m");
|
||||
break;
|
||||
|
||||
case TPM2_REPORT_TYPE_KEY_CERTIFICATION:
|
||||
/* The public area of a certified key is encoded like the signing key's, as TSS2
|
||||
* JSON. It carries everything a verifier needs to recompute the certified name and
|
||||
* to judge the key's attributes, and marshalled into a TPM2B_PUBLIC it is what
|
||||
* systemd-cryptenroll and systemd-repart consume via --tpm2-device-key=. */
|
||||
r = tpm2_tpmt_public_to_json(c->key_public, &key_public);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Cannot convert certified key public area to JSON: %m");
|
||||
break;
|
||||
|
||||
case TPM2_REPORT_TYPE_CAPABILITY_TPM_PROPERTY:
|
||||
r = tpm2_tpms_tagged_property_to_json(c->property, &tpm_prop);
|
||||
if (r < 0)
|
||||
@@ -838,6 +848,7 @@ static int build_report(sd_json_variant **v, const Tpm2Report *report, const str
|
||||
JSON_BUILD_PAIR_STRING_NON_EMPTY("nvpcrName", c->nv_pcr_name),
|
||||
JSON_BUILD_PAIR_VARIANT_NON_NULL("nvPublic", nv_public),
|
||||
JSON_BUILD_PAIR_STRING_NON_EMPTY("authenticatedData", c->authenticated_data),
|
||||
JSON_BUILD_PAIR_VARIANT_NON_NULL("keyPublic", key_public),
|
||||
JSON_BUILD_PAIR_CONDITION_BOOLEAN(
|
||||
IN_SET(c->type, TPM2_REPORT_TYPE_CAPABILITY_TPM_PROPERTY, TPM2_REPORT_TYPE_CAPABILITY_AUTH_POLICY),
|
||||
"capabilityMoreData", c->more_caps),
|
||||
@@ -860,6 +871,45 @@ static int build_report(sd_json_variant **v, const Tpm2Report *report, const str
|
||||
SD_JSON_BUILD_PAIR_VARIANT("components", cv));
|
||||
}
|
||||
|
||||
static bool srk_usable_for_certification(const TPM2B_PUBLIC *public) {
|
||||
int r;
|
||||
|
||||
assert(public);
|
||||
|
||||
/* Whatever object sits at the SRK handle gets certified. Judging its attributes is up to the
|
||||
* verifier of the report, and whether it can be authorized with a password session is left to
|
||||
* TPM2_Certify, the report generator leaves the component out if not. What we need to be sure of
|
||||
* up front is that we can calculate the key's name and encode its public area, so that a
|
||||
* pre-existing SRK we cannot handle (e.g. with a nameAlg other than SHA256, or an unusual symmetric
|
||||
* algorithm) doesn't fail the whole report. */
|
||||
|
||||
/* The one attribute we do insist on is noDA: TPM2_Certify authorizes the object with its auth
|
||||
* value, and if that isn't empty each attempt against an object without noDA counts towards the
|
||||
* TPM's dictionary attack lockout. Reports are requested over and over again, hence stay away from
|
||||
* such an object rather than lock the TPM up for everyone. SRKs created by us (or tpm2-tools) carry
|
||||
* noDA. */
|
||||
if (!FLAGS_SET(public->publicArea.objectAttributes, TPMA_OBJECT_NODA)) {
|
||||
log_notice("SRK is subject to dictionary attack protection, not certifying it in the report.");
|
||||
return false;
|
||||
}
|
||||
|
||||
TPM2B_NAME name;
|
||||
r = tpm2_calculate_pubkey_name(&public->publicArea, &name);
|
||||
if (r < 0) {
|
||||
log_notice_errno(r, "Cannot calculate name of SRK, not certifying it in the report: %m");
|
||||
return false;
|
||||
}
|
||||
|
||||
_cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
|
||||
r = tpm2_tpmt_public_to_json(&public->publicArea, &v);
|
||||
if (r < 0) {
|
||||
log_notice_errno(r, "Cannot encode SRK public area as JSON, not certifying it in the report: %m");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static int vl_method_sign(
|
||||
sd_varlink *link,
|
||||
sd_json_variant *parameters,
|
||||
@@ -930,6 +980,39 @@ static int vl_method_sign(
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to load any signing keys: %m");
|
||||
|
||||
/* Also certify the SRK with each signing key, and include its public area in the report. Secrets
|
||||
* sealed to a certified SRK (e.g. via the --tpm2-device-key= switch of systemd-cryptenroll or
|
||||
* systemd-repart) can only be unsealed by this TPM, hence a verifier of the report can use it to
|
||||
* encrypt data for this system specifically. As TPM2_Clear replaces the SRK, the certification is
|
||||
* also evidence that the TPM has not been cleared since the report was generated, as long as the
|
||||
* SRK stays the same. If there is no SRK yet we create one, like systemd-cryptenroll does, which
|
||||
* requires the owner hierarchy authorization to be empty. The report remains useful without the
|
||||
* certification, hence failure to acquire an SRK we can handle is not fatal, and as we serve a
|
||||
* single request per process it is not worth more than a notice either. */
|
||||
_cleanup_(tpm2_handle_freep) Tpm2Handle *srk_handle = NULL;
|
||||
_cleanup_(Esys_Freep) TPM2B_PUBLIC *srk_public = NULL;
|
||||
r = tpm2_get_or_create_srk(
|
||||
c,
|
||||
/* session= */ NULL,
|
||||
&srk_public,
|
||||
/* ret_name= */ NULL,
|
||||
/* ret_qname= */ NULL,
|
||||
&srk_handle);
|
||||
if (r < 0)
|
||||
log_notice_errno(r, "Failed to acquire SRK, not certifying it in the report: %m");
|
||||
else {
|
||||
if (r > 0)
|
||||
log_debug("Created and persisted a new SRK.");
|
||||
|
||||
if (srk_usable_for_certification(srk_public)) {
|
||||
opts.certify_key_public = newdup(TPMT_PUBLIC, &srk_public->publicArea, 1);
|
||||
if (!opts.certify_key_public)
|
||||
return log_oom();
|
||||
|
||||
opts.certify_key = TAKE_PTR(srk_handle);
|
||||
}
|
||||
}
|
||||
|
||||
/* Generate a signed TPM report for each signing key. */
|
||||
_cleanup_(sd_json_variant_unrefp) sd_json_variant *dv = NULL;
|
||||
FOREACH_ARRAY(key, keys, n_keys) {
|
||||
@@ -939,7 +1022,12 @@ static int vl_method_sign(
|
||||
return log_error_errno(r, "Failed to acquire a signing key handle for key '%s': %m", key->name);
|
||||
|
||||
_cleanup_(tpm2_report_freep) Tpm2Report *report = NULL;
|
||||
r = tpm2_generate_report(c, &opts, key_handle, &external_data, &report);
|
||||
r = tpm2_generate_report(
|
||||
c,
|
||||
&opts,
|
||||
key_handle,
|
||||
&external_data,
|
||||
&report);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to generate TPM report with key '%s': %m", key->name);
|
||||
|
||||
|
||||
Reference in new issue
Block a user