mirror of
https://github.com/systemd/systemd
synced 2024-07-21 02:05:05 +00:00
Merge pull request #25224 from poettering/measure-append
add --append= switch to systemd-measure
This commit is contained in:
commit
cd07f6e8e9
13
TODO
13
TODO
|
@ -251,19 +251,6 @@ Features:
|
|||
kernel. So far we only did this for the various --image= switches, but not
|
||||
for the root fs or /usr/.
|
||||
|
||||
* extend systemd-measure with an --append= mode when signing expected PCR
|
||||
measurements. In this mode the tool should read an existing signature JSON
|
||||
object (which primarily contains an array with the actual signature data),
|
||||
and then append the new signature to it instead of writing out an entirely
|
||||
JSON object. Usecase: it might make sense to to sign a UKI's expected PCRs
|
||||
with different keys for different boot phases. i.e. use keypair X for signing
|
||||
the expected PCR in the initrd boot phase and keypair Y for signing the
|
||||
expected PCR in the main boot phase. Via the --append logic we could merge
|
||||
these signatures into one object, and then include the result in the UKI.
|
||||
Then, if you bind a LUKS volume to public key X it really only can be
|
||||
unlocked during early boot, and you bind a LUKS volume to public key Y it
|
||||
really only can be unlocked during later boot, and so on.
|
||||
|
||||
* dissection policy should enforce that unlocking can only take place by
|
||||
certain means, i.e. only via pw, only via tpm2, or only via fido, or a
|
||||
combination thereof.
|
||||
|
|
|
@ -182,6 +182,19 @@
|
|||
<citerefentry><refentrytitle>systemd-pcrphase.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><option>--append=</option><replaceable>PATH</replaceable></term>
|
||||
|
||||
<listitem><para>When generating a PCR JSON signature (via the <command>sign</command> command),
|
||||
combine it with a previously generated PCR JSON signature, and output it as one. The specified path
|
||||
must refer to a regular file that contains a valid JSON PCR signature object. The specified file is
|
||||
not modified. It will be read first, then the newly generated signature appended to it, and the
|
||||
resulting object is written to standard output. Use this to generate a single JSON object consisting
|
||||
from signatures made with a number of signing keys (for example, to have one key per boot phase). The
|
||||
command will suppress duplicates: if a specific signature is already included in a JSON signature
|
||||
object it is not added a second time.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<xi:include href="standard-options.xml" xpointer="json" />
|
||||
<xi:include href="standard-options.xml" xpointer="no-pager" />
|
||||
<xi:include href="standard-options.xml" xpointer="help" />
|
||||
|
|
|
@ -33,12 +33,14 @@ static JsonFormatFlags arg_json_format_flags = JSON_FORMAT_PRETTY_AUTO|JSON_FORM
|
|||
static PagerFlags arg_pager_flags = 0;
|
||||
static bool arg_current = false;
|
||||
static char **arg_phase = NULL;
|
||||
static char *arg_append = NULL;
|
||||
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_banks, strv_freep);
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_tpm2_device, freep);
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_private_key, freep);
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_public_key, freep);
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_phase, strv_freep);
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_append, freep);
|
||||
|
||||
static inline void free_sections(char*(*sections)[_UNIFIED_SECTION_MAX]) {
|
||||
for (UnifiedSection c = 0; c < _UNIFIED_SECTION_MAX; c++)
|
||||
|
@ -73,6 +75,7 @@ static int help(int argc, char *argv[], void *userdata) {
|
|||
" --public-key=KEY Public key (PEM) to validate against\n"
|
||||
" --json=MODE Output as JSON\n"
|
||||
" -j Same as --json=pretty on tty, --json=short otherwise\n"
|
||||
" --append=PATH Load specified JSON signature, and append new signature to it\n"
|
||||
"\n%3$sUKI PE Section Options:%4$s %3$sUKI PE Section%4$s\n"
|
||||
" --linux=PATH Path to Linux kernel image file %7$s .linux\n"
|
||||
" --osrel=PATH Path to os-release file %7$s .osrel\n"
|
||||
|
@ -128,6 +131,7 @@ static int parse_argv(int argc, char *argv[]) {
|
|||
ARG_TPM2_DEVICE,
|
||||
ARG_JSON,
|
||||
ARG_PHASE,
|
||||
ARG_APPEND,
|
||||
};
|
||||
|
||||
static const struct option options[] = {
|
||||
|
@ -148,6 +152,7 @@ static int parse_argv(int argc, char *argv[]) {
|
|||
{ "public-key", required_argument, NULL, ARG_PUBLIC_KEY },
|
||||
{ "json", required_argument, NULL, ARG_JSON },
|
||||
{ "phase", required_argument, NULL, ARG_PHASE },
|
||||
{ "append", required_argument, NULL, ARG_APPEND },
|
||||
{}
|
||||
};
|
||||
|
||||
|
@ -254,6 +259,13 @@ static int parse_argv(int argc, char *argv[]) {
|
|||
break;
|
||||
}
|
||||
|
||||
case ARG_APPEND:
|
||||
r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_append);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
break;
|
||||
|
||||
case '?':
|
||||
return -EINVAL;
|
||||
|
||||
|
@ -623,6 +635,8 @@ static int verb_calculate(int argc, char *argv[], void *userdata) {
|
|||
|
||||
if (!arg_sections[UNIFIED_SECTION_LINUX] && !arg_current)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Either --linux= or --current must be specified, refusing.");
|
||||
if (arg_append)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "The --append= switch is only supported for 'sign', not 'calculate'.");
|
||||
|
||||
assert(!strv_isempty(arg_banks));
|
||||
assert(!strv_isempty(arg_phase));
|
||||
|
@ -728,6 +742,15 @@ static int verb_sign(int argc, char *argv[], void *userdata) {
|
|||
assert(!strv_isempty(arg_banks));
|
||||
assert(!strv_isempty(arg_phase));
|
||||
|
||||
if (arg_append) {
|
||||
r = json_parse_file(NULL, arg_append, 0, &v, NULL, NULL);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to parse '%s': %m", arg_append);
|
||||
|
||||
if (!json_variant_is_object(v))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "File '%s' is not a valid JSON object, refusing.", arg_append);
|
||||
}
|
||||
|
||||
/* When signing we only support JSON output */
|
||||
arg_json_format_flags &= ~JSON_FORMAT_OFF;
|
||||
|
||||
|
@ -936,7 +959,7 @@ static int verb_sign(int argc, char *argv[], void *userdata) {
|
|||
_cleanup_(json_variant_unrefp) JsonVariant *av = NULL;
|
||||
av = json_variant_ref(json_variant_by_key(v, p->bank));
|
||||
|
||||
r = json_variant_append_array(&av, bv);
|
||||
r = json_variant_append_array_nodup(&av, bv);
|
||||
if (r < 0) {
|
||||
log_error_errno(r, "Failed to append JSON object: %m");
|
||||
goto finish;
|
||||
|
|
|
@ -2093,7 +2093,6 @@ int json_variant_append_array(JsonVariant **v, JsonVariant *element) {
|
|||
assert(v);
|
||||
assert(element);
|
||||
|
||||
|
||||
if (!*v || json_variant_is_null(*v))
|
||||
blank = true;
|
||||
else if (json_variant_is_array(*v))
|
||||
|
@ -2151,6 +2150,27 @@ int json_variant_append_array(JsonVariant **v, JsonVariant *element) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
JsonVariant *json_variant_find(JsonVariant *haystack, JsonVariant *needle) {
|
||||
JsonVariant *i;
|
||||
|
||||
/* Find a json object in an array. Returns NULL if not found, or if the array is not actually an array. */
|
||||
|
||||
JSON_VARIANT_ARRAY_FOREACH(i, haystack)
|
||||
if (json_variant_equal(i, needle))
|
||||
return i;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int json_variant_append_array_nodup(JsonVariant **v, JsonVariant *element) {
|
||||
assert(v);
|
||||
|
||||
if (json_variant_find(*v, element))
|
||||
return 0;
|
||||
|
||||
return json_variant_append_array(v, element);
|
||||
}
|
||||
|
||||
int json_variant_strv(JsonVariant *v, char ***ret) {
|
||||
char **l = NULL;
|
||||
bool sensitive;
|
||||
|
|
|
@ -210,7 +210,10 @@ int json_variant_set_field_unsigned(JsonVariant **v, const char *field, uint64_t
|
|||
int json_variant_set_field_boolean(JsonVariant **v, const char *field, bool b);
|
||||
int json_variant_set_field_strv(JsonVariant **v, const char *field, char **l);
|
||||
|
||||
JsonVariant *json_variant_find(JsonVariant *haystack, JsonVariant *needle);
|
||||
|
||||
int json_variant_append_array(JsonVariant **v, JsonVariant *element);
|
||||
int json_variant_append_array_nodup(JsonVariant **v, JsonVariant *element);
|
||||
|
||||
int json_variant_merge(JsonVariant **v, JsonVariant *m);
|
||||
|
||||
|
|
|
@ -726,4 +726,29 @@ TEST(json_array_append_without_source) {
|
|||
json_array_append_with_source_one(false);
|
||||
}
|
||||
|
||||
TEST(json_array_append_nodup) {
|
||||
_cleanup_(json_variant_unrefp) JsonVariant *l = NULL, *s = NULL, *wd = NULL, *nd = NULL;
|
||||
|
||||
assert_se(json_build(&l, JSON_BUILD_STRV(STRV_MAKE("foo", "bar", "baz", "bar", "baz", "foo", "qux", "baz"))) >= 0);
|
||||
assert_se(json_build(&s, JSON_BUILD_STRV(STRV_MAKE("foo", "bar", "baz", "qux"))) >= 0);
|
||||
|
||||
assert_se(!json_variant_equal(l, s));
|
||||
assert_se(json_variant_elements(l) == 8);
|
||||
assert_se(json_variant_elements(s) == 4);
|
||||
|
||||
JsonVariant *i;
|
||||
JSON_VARIANT_ARRAY_FOREACH(i, l) {
|
||||
assert_se(json_variant_append_array(&wd, i) >= 0);
|
||||
assert_se(json_variant_append_array_nodup(&nd, i) >= 0);
|
||||
}
|
||||
|
||||
assert_se(json_variant_elements(wd) == 8);
|
||||
assert_se(json_variant_equal(l, wd));
|
||||
assert_se(!json_variant_equal(s, wd));
|
||||
|
||||
assert_se(json_variant_elements(nd) == 4);
|
||||
assert_se(!json_variant_equal(l, nd));
|
||||
assert_se(json_variant_equal(s, nd));
|
||||
}
|
||||
|
||||
DEFINE_TEST_MAIN(LOG_DEBUG);
|
||||
|
|
|
@ -150,6 +150,23 @@ if [ -e /usr/lib/systemd/systemd-measure ] && \
|
|||
SYSTEMD_CRYPTSETUP_USE_TOKEN_MODULE=1 /usr/lib/systemd/systemd-cryptsetup attach test-volume2 $img - tpm2-device=auto,tpm2-signature="/tmp/pcrsign.sig3",headless=1
|
||||
/usr/lib/systemd/systemd-cryptsetup detach test-volume2
|
||||
|
||||
# Test --append mode and de-duplication. With the same parameters signing should not add a new entry
|
||||
/usr/lib/systemd/systemd-measure sign --current "${MEASURE_BANKS[@]}" --private-key="/tmp/pcrsign-private.pem" --public-key="/tmp/pcrsign-public.pem" --phase=: --append="/tmp/pcrsign.sig3" > "/tmp/pcrsign.sig4"
|
||||
cmp "/tmp/pcrsign.sig3" "/tmp/pcrsign.sig4"
|
||||
|
||||
# Sign one more phase, this should
|
||||
/usr/lib/systemd/systemd-measure sign --current "${MEASURE_BANKS[@]}" --private-key="/tmp/pcrsign-private.pem" --public-key="/tmp/pcrsign-public.pem" --phase=quux:waldo --append="/tmp/pcrsign.sig4" > "/tmp/pcrsign.sig5"
|
||||
( ! cmp "/tmp/pcrsign.sig4" "/tmp/pcrsign.sig5" )
|
||||
|
||||
# Should still be good to unlock, given the old entry still exists
|
||||
SYSTEMD_CRYPTSETUP_USE_TOKEN_MODULE=0 /usr/lib/systemd/systemd-cryptsetup attach test-volume2 $img - tpm2-device=auto,tpm2-signature="/tmp/pcrsign.sig5",headless=1
|
||||
/usr/lib/systemd/systemd-cryptsetup detach test-volume2
|
||||
|
||||
# Adding both signatures once more shoud not change anything, due to the deduplication
|
||||
/usr/lib/systemd/systemd-measure sign --current "${MEASURE_BANKS[@]}" --private-key="/tmp/pcrsign-private.pem" --public-key="/tmp/pcrsign-public.pem" --phase=: --append="/tmp/pcrsign.sig5" > "/tmp/pcrsign.sig6"
|
||||
/usr/lib/systemd/systemd-measure sign --current "${MEASURE_BANKS[@]}" --private-key="/tmp/pcrsign-private.pem" --public-key="/tmp/pcrsign-public.pem" --phase=quux:waldo --append="/tmp/pcrsign.sig6" > "/tmp/pcrsign.sig7"
|
||||
cmp "/tmp/pcrsign.sig5" "/tmp/pcrsign.sig7"
|
||||
|
||||
rm $img
|
||||
else
|
||||
echo "/usr/lib/systemd/systemd-measure or PCR sysfs files not found, skipping signed PCR policy test case"
|
||||
|
|
Loading…
Reference in a new issue