Skip to content

Commit 2cc3097

Browse files
committed
chapter7: Add a worked example of configuration signing
Add a new section that walks through a concrete FIT to show exactly which parts are included in a configuration signature hash. The example shows the FIT source before signing, the result after signing (with filled-in hashes, signature and metadata), the node list, and a bold-annotated listing highlighting every hashed element. A strings-block illustration shows the property names appended to the hash. Signed-off-by: Simon Glass <sjg@chromium.org>
1 parent 61ab8a3 commit 2cc3097

File tree

1 file changed

+237
-0
lines changed

1 file changed

+237
-0
lines changed

source/chapter7-security.rst

Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,4 +179,241 @@ over the image's ``data`` property value only (i.e. the raw image content,
179179
not any FDT metadata). The algorithm is given by the hash node's ``algo``
180180
property and the resulting digest is stored in its ``value`` property.
181181

182+
Worked example
183+
~~~~~~~~~~~~~~
184+
185+
This section walks through a concrete FIT to show exactly which bytes are
186+
included in a configuration signature hash.
187+
188+
Source
189+
^^^^^^
190+
191+
Consider the following minimal FIT source::
192+
193+
/ {
194+
description = "Example FIT";
195+
#address-cells = <1>;
196+
197+
images {
198+
kernel {
199+
data = /incbin/("vmlinuz");
200+
type = "kernel";
201+
arch = "arm64";
202+
os = "linux";
203+
compression = "none";
204+
load = <0x40000000>;
205+
entry = <0x40000000>;
206+
hash-1 {
207+
algo = "sha256";
208+
};
209+
};
210+
fdt-1 {
211+
data = /incbin/("board.dtb");
212+
type = "flat_dt";
213+
arch = "arm64";
214+
compression = "none";
215+
hash-1 {
216+
algo = "sha256";
217+
};
218+
};
219+
};
220+
configurations {
221+
default = "conf-1";
222+
conf-1 {
223+
description = "Boot Linux";
224+
compatible = "vendor,board";
225+
kernel = "kernel";
226+
fdt = "fdt-1";
227+
signature-1 {
228+
algo = "sha256,rsa2048";
229+
key-name-hint = "dev";
230+
sign-images = "kernel", "fdt";
231+
};
232+
};
233+
};
234+
};
235+
236+
After signing
237+
^^^^^^^^^^^^^
238+
239+
During signing, the signer adds a ``value`` property to each hash node
240+
containing the image digest, and adds ``value``, ``hashed-nodes``,
241+
``hashed-strings`` and other properties to the signature node. The
242+
resulting FIT looks like this::
243+
244+
/ {
245+
description = "Example FIT";
246+
timestamp = <0x67d96bac>;
247+
#address-cells = <1>;
248+
249+
images {
250+
kernel {
251+
data = <...kernel data...>;
252+
type = "kernel";
253+
arch = "arm64";
254+
os = "linux";
255+
compression = "none";
256+
load = <0x40000000>;
257+
entry = <0x40000000>;
258+
hash-1 {
259+
algo = "sha256";
260+
value = <...32-byte SHA-256 digest of kernel data...>;
261+
};
262+
};
263+
fdt-1 {
264+
data = <...devicetree data...>;
265+
type = "flat_dt";
266+
arch = "arm64";
267+
compression = "none";
268+
hash-1 {
269+
algo = "sha256";
270+
value = <...32-byte SHA-256 digest of devicetree data...>;
271+
};
272+
};
273+
};
274+
configurations {
275+
default = "conf-1";
276+
conf-1 {
277+
description = "Boot Linux";
278+
compatible = "vendor,board";
279+
kernel = "kernel";
280+
fdt = "fdt-1";
281+
signature-1 {
282+
algo = "sha256,rsa2048";
283+
key-name-hint = "dev";
284+
sign-images = "kernel", "fdt";
285+
value = <...256-byte RSA-2048 signature...>;
286+
hashed-nodes = "/", "/configurations/conf-1",
287+
"/images/kernel", "/images/kernel/hash-1",
288+
"/images/fdt-1", "/images/fdt-1/hash-1";
289+
hashed-strings = <0x00000000 0x000000d4>;
290+
timestamp = <0x67d96bac>;
291+
signer-name = "mkimage";
292+
signer-version = "2025.04-rc3";
293+
};
294+
};
295+
};
296+
};
297+
298+
Node list
299+
^^^^^^^^^
300+
301+
For the configuration signature ``/configurations/conf-1/signature-1``, the
302+
node list is:
303+
304+
- ``/``
305+
- ``/configurations/conf-1``
306+
- ``/images/kernel``
307+
- ``/images/kernel/hash-1``
308+
- ``/images/fdt-1``
309+
- ``/images/fdt-1/hash-1``
310+
311+
What is hashed
312+
^^^^^^^^^^^^^^
313+
314+
The following shows the signed FIT with **bold** indicating the parts that are
315+
included in the configuration signature hash. Lines in normal weight are not
316+
hashed. Note that node braces (``{`` and ``}``) represent ``FDT_BEGIN_NODE``
317+
and ``FDT_END_NODE`` tokens respectively; these are included whenever the node
318+
or its parent is in the node list.
319+
320+
.. parsed-literal::
321+
322+
**/ {**
323+
**description = "Example FIT";**
324+
**timestamp = <0x67d96bac>;**
325+
**#address-cells = <1>;**
326+
327+
**images {**
328+
**kernel {**
329+
data = <...kernel data...>;
330+
**type = "kernel";**
331+
**arch = "arm64";**
332+
**os = "linux";**
333+
**compression = "none";**
334+
**load = <0x40000000>;**
335+
**entry = <0x40000000>;**
336+
**hash-1 {**
337+
**algo = "sha256";**
338+
**value = <...32-byte SHA-256 digest...>;**
339+
**};**
340+
**};**
341+
**fdt-1 {**
342+
data = <...devicetree data...>;
343+
**type = "flat_dt";**
344+
**arch = "arm64";**
345+
**compression = "none";**
346+
**hash-1 {**
347+
**algo = "sha256";**
348+
**value = <...32-byte SHA-256 digest...>;**
349+
**};**
350+
**};**
351+
**};**
352+
**configurations {**
353+
default = "conf-1";
354+
**conf-1 {**
355+
**description = "Boot Linux";**
356+
**compatible = "vendor,board";**
357+
**kernel = "kernel";**
358+
**fdt = "fdt-1";**
359+
**signature-1 {**
360+
algo = "sha256,rsa2048";
361+
key-name-hint = "dev";
362+
sign-images = "kernel", "fdt";
363+
value = <...256-byte RSA-2048 signature...>;
364+
hashed-nodes = "/", "/configurations/conf-1", ...;
365+
hashed-strings = <0x00000000 0x000000d4>;
366+
timestamp = <0x67d96bac>;
367+
signer-name = "mkimage";
368+
signer-version = "2025.04-rc3";
369+
**};**
370+
**};**
371+
**};**
372+
**};**
373+
374+
Strings block:
375+
**description\\0**
376+
**timestamp\\0**
377+
**#address-cells\\0**
378+
**type\\0**
379+
**arch\\0**
380+
**os\\0**
381+
**compression\\0**
382+
**load\\0**
383+
**entry\\0**
384+
**algo\\0**
385+
**value\\0**
386+
**compatible\\0**
387+
**kernel\\0**
388+
**fdt\\0**
389+
default\\0
390+
padding\\0
391+
392+
Key points to note:
393+
394+
- The ``data`` properties of both image nodes are excluded since image-data
395+
integrity is verified separately through the hash nodes.
396+
- The ``default`` property of the ``configurations`` node is not hashed because
397+
that node is not in the node list (only its parent ``/`` is). This is safe
398+
because the bootloader selects a configuration by its own logic, not by
399+
trusting the default.
400+
- All properties of ``signature-1`` are excluded because that node is not in the
401+
node list. Its braces are included because its parent ``conf-1`` is. This is
402+
safe because the signature itself is verified against a trusted public key,
403+
not by hashing.
404+
- The ``images`` and ``configurations`` nodes have no properties of their own,
405+
but their braces are included because their parent ``/`` is in the node list.
406+
This serves as a structural sanity check, ensuring that an attacker cannot
407+
inject unexpected nodes into the tree without detection.
408+
- The strings-block region contains the property name strings referenced by the
409+
hashed nodes. Although the string-table offset in each ``FDT_PROP`` token is
410+
hashed, the string at that offset must also be protected; otherwise an
411+
attacker could rename a property (e.g. changing ``algo`` to something
412+
unrecognised) to trick the bootloader into skipping verification. The hashed
413+
region should therefore always start at offset 0.
414+
415+
The complete byte sequence (structure-block regions plus strings-block region)
416+
is hashed with SHA-256. The resulting digest is then signed with the RSA-2048
417+
private key to produce the signature ``value``.
418+
182419
.. sectionauthor:: Simon Glass <sjg@chromium.org>

0 commit comments

Comments
 (0)