<xi:include href="version-info.xml" xpointer="v257"/></listitem>
</varlistentry>
+ <varlistentry>
+ <term><option>--measure-base=<replaceable>PATH</replaceable></option></term>
+
+ <listitem><para>Takes a path to an existing PE file to use as base profile, for measuring
+ multi-profile UKIs. When calculating the PCR values, this has the effect that the sections
+ specified on the command line are combined with any sections from the PE file specified here (up to
+ the first <literal>.profile</literal> section, and only if not already specified on the command
+ line). Typically, this is used together with <option>--extend=</option> to both import and use as
+ measurement base an existing UKI.</para>
+
+ <xi:include href="version-info.xml" xpointer="v257"/></listitem>
+ </varlistentry>
+
<varlistentry>
<term><option>--tools=<replaceable>DIRS</replaceable></option></term>
def call_systemd_measure(uki, linux, opts):
+
+ if not opts.measure and not opts.pcr_private_keys:
+ return
+
+ measure_sections = ('.linux', '.osrel', '.cmdline', '.initrd',
+ '.ucode', '.splash', '.dtb', '.uname',
+ '.sbat', '.pcrpkey', '.profile')
+
measure_tool = find_tool('systemd-measure',
'/usr/lib/systemd/systemd-measure',
opts=opts)
# PCR measurement
+ to_measure = []
+ tflist = []
+
+ # First, pick up the sections we shall measure now */
+ for s in uki.sections:
+
+ print(s)
+
+ if not s.measure:
+ continue
+
+ if s.content is not None:
+ assert(s.name != ".linux" or linux is None)
+ to_measure.append(f"--{s.name.removeprefix('.')}={s.content}")
+ else:
+ raise ValueError(f"Don't know how to measure section {s.name}");
+
+ if linux is not None:
+ to_measure.append(f'--linux={linux}')
+
+ # And now iterate through the base profile and measure what we haven't measured above
+ if opts.measure_base is not None:
+ pe = pefile.PE(opts.measure_base, fast_load=True)
+
+ # Find matching PE section in base image
+ for base_section in pe.sections:
+ name = pe_strip_section_name(base_section.Name)
+
+ # If we reach the first .profile section the base is over
+ if name == ".profile":
+ break
+
+ # Only some sections are measured
+ if name not in measure_sections:
+ continue
+
+ # Check if this is a section we already covered above
+ already_covered = False
+ for s in uki.sections:
+ if s.measure and name == s.name:
+ already_covered = True
+ break;
+
+ if already_covered:
+ continue
+
+ # Split out section and use as base
+ tf = tempfile.NamedTemporaryFile()
+ tf.write(base_section.get_data(length=base_section.Misc_VirtualSize))
+ tf.flush()
+ tflist.append(tf)
+
+ to_measure.append(f"--{name.removeprefix('.')}={tf.name}")
+
if opts.measure:
pp_groups = opts.phase_path_groups or []
cmd = [
measure_tool,
'calculate',
- f'--linux={linux}',
- *(f"--{s.name.removeprefix('.')}={s.content}"
- for s in uki.sections
- if s.measure),
+ *to_measure,
*(f'--bank={bank}'
for bank in banks),
# For measurement, the keys are not relevant, so we can lump all the phase paths
cmd = [
measure_tool,
'sign',
- f'--linux={linux}',
- *(f"--{s.name.removeprefix('.')}={s.content}"
- for s in uki.sections
- if s.measure),
+ *to_measure,
*(f'--bank={bank}'
for bank in banks),
]
config_key = 'UKI/Extend',
),
+ ConfigItem(
+ '--measure-base',
+ metavar = 'UKI',
+ type = pathlib.Path,
+ help = 'path to existing UKI file whose relevant sections shall be used as base for PCR11 prediction',
+ config_key = 'UKI/MeasureBase',
+ ),
+
ConfigItem(
'--pcr-banks',
metavar = 'BANKā¦',