]>
git.ipfire.org Git - thirdparty/systemd.git/blob - src/ukify/test/test_ukify.py
2 # SPDX-License-Identifier: LGPL-2.1-or-later
4 # pylint: disable=unused-import,import-outside-toplevel,useless-else-on-loop
5 # pylint: disable=consider-using-with,wrong-import-position,unspecified-encoding
6 # pylint: disable=protected-access,redefined-outer-name
21 except ImportError as e
:
22 print(str(e
), file=sys
.stderr
)
28 except ImportError as e
:
29 print(str(e
), file=sys
.stderr
)
32 # We import ukify.py, which is a template file. But only __version__ is
33 # substituted, which we don't care about here. Having the .py suffix makes it
34 # easier to import the file.
35 sys
.path
.append(os
.path
.dirname(__file__
) + '/..')
38 build_root
= os
.getenv('PROJECT_BUILD_ROOT')
39 arg_tools
= ['--tools', build_root
] if build_root
else []
41 def systemd_measure():
42 opts
= ukify
.create_parser().parse_args(arg_tools
)
43 return ukify
.find_tool('systemd-measure', opts
=opts
)
45 def test_guess_efi_arch():
46 arch
= ukify
.guess_efi_arch()
47 assert arch
in ukify
.EFI_ARCHES
49 def test_shell_join():
50 assert ukify
.shell_join(['a', 'b', ' ']) == "a b ' '"
53 assert ukify
.round_up(0) == 0
54 assert ukify
.round_up(4095) == 4096
55 assert ukify
.round_up(4096) == 4096
56 assert ukify
.round_up(4097) == 8192
58 def test_namespace_creation():
59 ns
= ukify
.create_parser().parse_args(())
60 assert ns
.linux
is None
61 assert ns
.initrd
is None
63 def test_config_example():
64 ex
= ukify
.config_example()
66 assert 'Splash = BMP' in ex
68 def test_apply_config(tmp_path
):
69 config
= tmp_path
/ 'config1.conf'
70 config
.write_text(textwrap
.dedent(
74 Initrd = initrd1 initrd2
78 OSRelease = @some/path1
79 DeviceTree = some/path2
84 PCRBanks = sha512,sha1
85 SigningEngine = engine1
86 SecureBootPrivateKey = some/path5
87 SecureBootCertificate = some/path6
91 PCRPrivateKey = some/path7
92 PCRPublicKey = some/path8
93 Phases = {':'.join(ukify.KNOWN_PHASES)}
96 ns
= ukify
.create_parser().parse_args(['build'])
99 ukify
.apply_config(ns
, config
)
101 assert ns
.linux
== pathlib
.Path('LINUX')
102 assert ns
.initrd
== [pathlib
.Path('initrd1'),
103 pathlib
.Path('initrd2'),
104 pathlib
.Path('initrd3')]
105 assert ns
.cmdline
== '1 2 3 4 5\n6 7 8'
106 assert ns
.os_release
== '@some/path1'
107 assert ns
.devicetree
== pathlib
.Path('some/path2')
108 assert ns
.splash
== pathlib
.Path('some/path3')
109 assert ns
.efi_arch
== 'arm'
110 assert ns
.stub
== pathlib
.Path('some/path4')
111 assert ns
.pcr_banks
== ['sha512', 'sha1']
112 assert ns
.signing_engine
== 'engine1'
113 assert ns
.sb_key
== 'some/path5'
114 assert ns
.sb_cert
== 'some/path6'
115 assert ns
.sign_kernel
is False
117 assert ns
._groups
== ['NAME']
118 assert ns
.pcr_private_keys
== [pathlib
.Path('some/path7')]
119 assert ns
.pcr_public_keys
== [pathlib
.Path('some/path8')]
120 assert ns
.phase_path_groups
== [['enter-initrd:leave-initrd:sysinit:ready:shutdown:final']]
122 ukify
.finalize_options(ns
)
124 assert ns
.linux
== pathlib
.Path('LINUX')
125 assert ns
.initrd
== [pathlib
.Path('initrd1'),
126 pathlib
.Path('initrd2'),
127 pathlib
.Path('initrd3')]
128 assert ns
.cmdline
== '1 2 3 4 5 6 7 8'
129 assert ns
.os_release
== pathlib
.Path('some/path1')
130 assert ns
.devicetree
== pathlib
.Path('some/path2')
131 assert ns
.splash
== pathlib
.Path('some/path3')
132 assert ns
.efi_arch
== 'arm'
133 assert ns
.stub
== pathlib
.Path('some/path4')
134 assert ns
.pcr_banks
== ['sha512', 'sha1']
135 assert ns
.signing_engine
== 'engine1'
136 assert ns
.sb_key
== 'some/path5'
137 assert ns
.sb_cert
== 'some/path6'
138 assert ns
.sign_kernel
is False
140 assert ns
._groups
== ['NAME']
141 assert ns
.pcr_private_keys
== [pathlib
.Path('some/path7')]
142 assert ns
.pcr_public_keys
== [pathlib
.Path('some/path8')]
143 assert ns
.phase_path_groups
== [['enter-initrd:leave-initrd:sysinit:ready:shutdown:final']]
145 def test_parse_args_minimal():
146 with pytest
.raises(ValueError):
149 opts
= ukify
.parse_args('arg1 arg2'.split())
150 assert opts
.linux
== pathlib
.Path('arg1')
151 assert opts
.initrd
== [pathlib
.Path('arg2')]
152 assert opts
.os_release
in (pathlib
.Path('/etc/os-release'),
153 pathlib
.Path('/usr/lib/os-release'))
155 def test_parse_args_many_deprecated():
156 opts
= ukify
.parse_args(
157 ['/ARG1', '///ARG2', '/ARG3 WITH SPACE',
159 '--os-release=K1=V1\nK2=V2',
160 '--devicetree=DDDDTTTT',
165 '--pcr-private-key=PKEY1',
166 '--pcr-public-key=PKEY2',
167 '--pcr-banks=SHA1,SHA256',
168 '--signing-engine=ENGINE',
169 '--secureboot-private-key=SBKEY',
170 '--secureboot-certificate=SBCERT',
178 assert opts
.linux
== pathlib
.Path('/ARG1')
179 assert opts
.initrd
== [pathlib
.Path('/ARG2'), pathlib
.Path('/ARG3 WITH SPACE')]
180 assert opts
.cmdline
== 'a b c'
181 assert opts
.os_release
== 'K1=V1\nK2=V2'
182 assert opts
.devicetree
== pathlib
.Path('DDDDTTTT')
183 assert opts
.splash
== pathlib
.Path('splash')
184 assert opts
.pcrpkey
== pathlib
.Path('PATH')
185 assert opts
.uname
== '1.2.3'
186 assert opts
.stub
== pathlib
.Path('STUBPATH')
187 assert opts
.pcr_private_keys
== [pathlib
.Path('PKEY1')]
188 assert opts
.pcr_public_keys
== [pathlib
.Path('PKEY2')]
189 assert opts
.pcr_banks
== ['SHA1', 'SHA256']
190 assert opts
.signing_engine
== 'ENGINE'
191 assert opts
.sb_key
== 'SBKEY'
192 assert opts
.sb_cert
== 'SBCERT'
193 assert opts
.sign_kernel
is False
194 assert opts
.tools
== [pathlib
.Path('TOOLZ/')]
195 assert opts
.output
== pathlib
.Path('OUTPUT')
196 assert opts
.measure
is False
198 def test_parse_args_many():
199 opts
= ukify
.parse_args(
203 '--initrd=/ARG3 WITH SPACE',
205 '--os-release=K1=V1\nK2=V2',
206 '--devicetree=DDDDTTTT',
211 '--pcr-private-key=PKEY1',
212 '--pcr-public-key=PKEY2',
213 '--pcr-banks=SHA1,SHA256',
214 '--signing-engine=ENGINE',
215 '--secureboot-private-key=SBKEY',
216 '--secureboot-certificate=SBCERT',
224 assert opts
.linux
== pathlib
.Path('/ARG1')
225 assert opts
.initrd
== [pathlib
.Path('/ARG2'), pathlib
.Path('/ARG3 WITH SPACE')]
226 assert opts
.cmdline
== 'a b c'
227 assert opts
.os_release
== 'K1=V1\nK2=V2'
228 assert opts
.devicetree
== pathlib
.Path('DDDDTTTT')
229 assert opts
.splash
== pathlib
.Path('splash')
230 assert opts
.pcrpkey
== pathlib
.Path('PATH')
231 assert opts
.uname
== '1.2.3'
232 assert opts
.stub
== pathlib
.Path('STUBPATH')
233 assert opts
.pcr_private_keys
== [pathlib
.Path('PKEY1')]
234 assert opts
.pcr_public_keys
== [pathlib
.Path('PKEY2')]
235 assert opts
.pcr_banks
== ['SHA1', 'SHA256']
236 assert opts
.signing_engine
== 'ENGINE'
237 assert opts
.sb_key
== 'SBKEY'
238 assert opts
.sb_cert
== 'SBCERT'
239 assert opts
.sign_kernel
is False
240 assert opts
.tools
== [pathlib
.Path('TOOLZ/')]
241 assert opts
.output
== pathlib
.Path('OUTPUT')
242 assert opts
.measure
is False
244 def test_parse_sections():
245 opts
= ukify
.parse_args(
249 '--section=test:TESTTESTTEST',
250 '--section=test2:@FILE',
253 assert opts
.linux
== pathlib
.Path('/ARG1')
254 assert opts
.initrd
== [pathlib
.Path('/ARG2')]
255 assert len(opts
.sections
) == 2
257 assert opts
.sections
[0].name
== 'test'
258 assert isinstance(opts
.sections
[0].content
, pathlib
.Path
)
259 assert opts
.sections
[0].tmpfile
260 assert opts
.sections
[0].measure
is False
262 assert opts
.sections
[1].name
== 'test2'
263 assert opts
.sections
[1].content
== pathlib
.Path('FILE')
264 assert opts
.sections
[1].tmpfile
is None
265 assert opts
.sections
[1].measure
is False
267 def test_config_priority(tmp_path
):
268 config
= tmp_path
/ 'config1.conf'
269 # config: use pesign and give certdir + certname
270 config
.write_text(textwrap
.dedent(
274 Initrd = initrd1 initrd2
278 OSRelease = @some/path1
279 DeviceTree = some/path2
284 PCRBanks = sha512,sha1
285 SigningEngine = engine1
286 SecureBootSigningTool = pesign
287 SecureBootCertificateDir = some/path5
288 SecureBootCertificateName = some/name1
292 PCRPrivateKey = some/path7
293 PCRPublicKey = some/path8
294 Phases = {':'.join(ukify.KNOWN_PHASES)}
297 # args: use sbsign and give key + cert, should override pesign
298 opts
= ukify
.parse_args(
302 '--initrd=/ARG3 WITH SPACE',
304 '--os-release=K1=V1\nK2=V2',
305 '--devicetree=DDDDTTTT',
310 '--pcr-private-key=PKEY1',
311 '--pcr-public-key=PKEY2',
312 '--pcr-banks=SHA1,SHA256',
313 '--signing-engine=ENGINE',
315 '--secureboot-private-key=SBKEY',
316 '--secureboot-certificate=SBCERT',
324 ukify
.apply_config(opts
, config
)
325 ukify
.finalize_options(opts
)
327 assert opts
.linux
== pathlib
.Path('/ARG1')
328 assert opts
.initrd
== [pathlib
.Path('initrd1'),
329 pathlib
.Path('initrd2'),
330 pathlib
.Path('initrd3'),
331 pathlib
.Path('/ARG2'),
332 pathlib
.Path('/ARG3 WITH SPACE')]
333 assert opts
.cmdline
== 'a b c'
334 assert opts
.os_release
== 'K1=V1\nK2=V2'
335 assert opts
.devicetree
== pathlib
.Path('DDDDTTTT')
336 assert opts
.splash
== pathlib
.Path('splash')
337 assert opts
.pcrpkey
== pathlib
.Path('PATH')
338 assert opts
.uname
== '1.2.3'
339 assert opts
.stub
== pathlib
.Path('STUBPATH')
340 assert opts
.pcr_private_keys
== [pathlib
.Path('PKEY1'),
341 pathlib
.Path('some/path7')]
342 assert opts
.pcr_public_keys
== [pathlib
.Path('PKEY2'),
343 pathlib
.Path('some/path8')]
344 assert opts
.pcr_banks
== ['SHA1', 'SHA256']
345 assert opts
.signing_engine
== 'ENGINE'
346 assert opts
.signtool
== 'sbsign' # from args
347 assert opts
.sb_key
== 'SBKEY' # from args
348 assert opts
.sb_cert
== 'SBCERT' # from args
349 assert opts
.sb_certdir
== 'some/path5' # from config
350 assert opts
.sb_cert_name
== 'some/name1' # from config
351 assert opts
.sign_kernel
is False
352 assert opts
.tools
== [pathlib
.Path('TOOLZ/')]
353 assert opts
.output
== pathlib
.Path('OUTPUT')
354 assert opts
.measure
is True
356 def test_help(capsys
):
357 with pytest
.raises(SystemExit):
358 ukify
.parse_args(['--help'])
359 out
= capsys
.readouterr()
360 assert '--section' in out
.out
363 def test_help_display(capsys
):
364 with pytest
.raises(SystemExit):
365 ukify
.parse_args(['inspect', '--help'])
366 out
= capsys
.readouterr()
367 assert '--section' in out
.out
370 def test_help_error_deprecated(capsys
):
371 with pytest
.raises(SystemExit):
372 ukify
.parse_args(['a', 'b', '--no-such-option'])
373 out
= capsys
.readouterr()
375 assert '--no-such-option' in out
.err
376 assert len(out
.err
.splitlines()) == 1
378 def test_help_error(capsys
):
379 with pytest
.raises(SystemExit):
380 ukify
.parse_args(['build', '--no-such-option'])
381 out
= capsys
.readouterr()
383 assert '--no-such-option' in out
.err
384 assert len(out
.err
.splitlines()) == 1
386 @pytest.fixture(scope
='session')
388 opts
= ukify
.create_parser().parse_args(arg_tools
)
389 bootctl
= ukify
.find_tool('bootctl', opts
=opts
)
394 text
= subprocess
.check_output([bootctl
, 'list', '--json=short'],
396 except subprocess
.CalledProcessError
:
399 items
= json
.loads(text
)
403 linux
= f
"{item['root']}{item['linux']}"
404 initrd
= f
"{item['root']}{item['initrd'][0].split(' ')[0]}"
405 except (KeyError, IndexError):
407 return ['--linux', linux
, '--initrd', initrd
]
411 def test_check_splash():
416 pytest
.skip('PIL not available')
418 with pytest
.raises(OSError):
419 ukify
.check_splash(os
.devnull
)
421 def test_basic_operation(kernel_initrd
, tmp_path
):
422 if kernel_initrd
is None:
423 pytest
.skip('linux+initrd not found')
425 output
= f
'{tmp_path}/basic.efi'
426 opts
= ukify
.parse_args([
429 f
'--output={output}',
432 ukify
.check_inputs(opts
)
438 # let's check that objdump likes the resulting file
439 subprocess
.check_output(['objdump', '-h', output
])
441 shutil
.rmtree(tmp_path
)
443 def test_sections(kernel_initrd
, tmp_path
):
444 if kernel_initrd
is None:
445 pytest
.skip('linux+initrd not found')
447 output
= f
'{tmp_path}/basic.efi'
448 opts
= ukify
.parse_args([
451 f
'--output={output}',
453 '--cmdline=ARG1 ARG2 ARG3',
454 '--os-release=K1=V1\nK2=V2\n',
455 '--section=.test:CONTENTZ',
459 ukify
.check_inputs(opts
)
465 # let's check that objdump likes the resulting file
466 dump
= subprocess
.check_output(['objdump', '-h', output
], text
=True)
468 for sect
in 'text osrel cmdline linux initrd uname test'.split():
469 assert re
.search(fr
'^\s*\d+\s+\.{sect}\s+[0-9a-f]+', dump
, re
.MULTILINE
)
471 shutil
.rmtree(tmp_path
)
473 def test_addon(tmp_path
):
474 output
= f
'{tmp_path}/addon.efi'
477 f
'--output={output}',
478 '--cmdline=ARG1 ARG2 ARG3',
483 '--section=.test:CONTENTZ',
488 if stub
:= os
.getenv('EFI_ADDON'):
489 args
+= [f
'--stub={stub}']
490 expected_exceptions
= ()
492 expected_exceptions
= (FileNotFoundError
,)
494 opts
= ukify
.parse_args(args
)
496 ukify
.check_inputs(opts
)
497 except expected_exceptions
as e
:
502 # let's check that objdump likes the resulting file
503 dump
= subprocess
.check_output(['objdump', '-h', output
], text
=True)
505 for sect
in 'text cmdline test sbat'.split():
506 assert re
.search(fr
'^\s*\d+\s+\.{sect}\s+[0-9a-f]+', dump
, re
.MULTILINE
)
508 pe
= pefile
.PE(output
, fast_load
=True)
511 for section
in pe
.sections
:
512 if section
.Name
.rstrip(b
"\x00").decode() == ".sbat":
513 assert found
is False
514 split
= section
.get_data().rstrip(b
"\x00").decode().splitlines()
515 assert split
== ["sbat,1,SBAT Version,sbat,1,https://github.com/rhboot/shim/blob/main/SBAT.md", "foo,1", "bar,2", "baz,3"]
520 def unbase64(filename
):
521 tmp
= tempfile
.NamedTemporaryFile()
522 base64
.decode(filename
.open('rb'), tmp
)
526 def test_uname_scraping(kernel_initrd
):
527 if kernel_initrd
is None:
528 pytest
.skip('linux+initrd not found')
530 assert kernel_initrd
[0] == '--linux'
531 uname
= ukify
.Uname
.scrape(kernel_initrd
[1])
532 assert re
.match(r
'\d+\.\d+\.\d+', uname
)
534 @pytest.mark
.parametrize("days", [365*10, None])
535 def test_efi_signing_sbsign(days
, kernel_initrd
, tmp_path
):
536 if kernel_initrd
is None:
537 pytest
.skip('linux+initrd not found')
538 if not shutil
.which('sbsign'):
539 pytest
.skip('sbsign not found')
541 ourdir
= pathlib
.Path(__file__
).parent
542 cert
= unbase64(ourdir
/ 'example.signing.crt.base64')
543 key
= unbase64(ourdir
/ 'example.signing.key.base64')
545 output
= f
'{tmp_path}/signed.efi'
549 f
'--output={output}',
551 '--cmdline=ARG1 ARG2 ARG3',
552 f
'--secureboot-certificate={cert.name}',
553 f
'--secureboot-private-key={key.name}',
556 args
+= [f
'--secureboot-certificate-validity={days}']
558 opts
= ukify
.parse_args(args
)
561 ukify
.check_inputs(opts
)
567 if shutil
.which('sbverify'):
568 # let's check that sbverify likes the resulting file
569 dump
= subprocess
.check_output([
575 assert 'Signature verification OK' in dump
577 shutil
.rmtree(tmp_path
)
579 def test_efi_signing_pesign(kernel_initrd
, tmp_path
):
580 if kernel_initrd
is None:
581 pytest
.skip('linux+initrd not found')
582 if not shutil
.which('pesign'):
583 pytest
.skip('pesign not found')
585 nss_db
= f
'{tmp_path}/nss_db'
586 name
= 'Test_Secureboot'
589 subprocess
.check_call(['mkdir', '-p', nss_db
])
590 cmd
= f
'certutil -N --empty-password -d {nss_db}'.split(' ')
591 subprocess
.check_call(cmd
)
592 cmd
= f
'efikeygen -d {nss_db} -S -k -c CN={author} -n {name}'.split(' ')
593 subprocess
.check_call(cmd
)
595 output
= f
'{tmp_path}/signed.efi'
596 opts
= ukify
.parse_args([
599 f
'--output={output}',
602 '--cmdline=ARG1 ARG2 ARG3',
603 f
'--secureboot-certificate-name={name}',
604 f
'--secureboot-certificate-dir={nss_db}',
608 ukify
.check_inputs(opts
)
614 # let's check that sbverify likes the resulting file
615 dump
= subprocess
.check_output([
620 assert f
"The signer's common name is {author}" in dump
622 shutil
.rmtree(tmp_path
)
624 def test_inspect(kernel_initrd
, tmp_path
, capsys
):
625 if kernel_initrd
is None:
626 pytest
.skip('linux+initrd not found')
627 if not shutil
.which('sbsign'):
628 pytest
.skip('sbsign not found')
630 ourdir
= pathlib
.Path(__file__
).parent
631 cert
= unbase64(ourdir
/ 'example.signing.crt.base64')
632 key
= unbase64(ourdir
/ 'example.signing.key.base64')
634 output
= f
'{tmp_path}/signed2.efi'
637 cmdline_arg
='ARG1 ARG2 ARG3'
638 opts
= ukify
.parse_args([
641 f
'--cmdline={cmdline_arg}',
642 f
'--os-release={osrel_arg}',
643 f
'--uname={uname_arg}',
644 f
'--output={output}',
645 f
'--secureboot-certificate={cert.name}',
646 f
'--secureboot-private-key={key.name}',
649 ukify
.check_inputs(opts
)
652 opts
= ukify
.parse_args(['inspect', output
])
653 ukify
.inspect_sections(opts
)
655 text
= capsys
.readouterr().out
657 expected_osrel
= f
'.osrel:\n size: {len(osrel_arg)}'
658 assert expected_osrel
in text
659 expected_cmdline
= f
'.cmdline:\n size: {len(cmdline_arg)}'
660 assert expected_cmdline
in text
661 expected_uname
= f
'.uname:\n size: {len(uname_arg)}'
662 assert expected_uname
in text
664 expected_initrd
= '.initrd:\n size:'
665 assert expected_initrd
in text
666 expected_linux
= '.linux:\n size:'
667 assert expected_linux
in text
669 shutil
.rmtree(tmp_path
)
671 def test_pcr_signing(kernel_initrd
, tmp_path
):
672 if kernel_initrd
is None:
673 pytest
.skip('linux+initrd not found')
674 if systemd_measure() is None:
675 pytest
.skip('systemd-measure not found')
677 ourdir
= pathlib
.Path(__file__
).parent
678 pub
= unbase64(ourdir
/ 'example.tpm2-pcr-public.pem.base64')
679 priv
= unbase64(ourdir
/ 'example.tpm2-pcr-private.pem.base64')
681 output
= f
'{tmp_path}/signed.efi'
685 f
'--output={output}',
687 '--cmdline=ARG1 ARG2 ARG3',
688 '--os-release=ID=foobar\n',
689 '--pcr-banks=sha1', # use sha1 because it doesn't really matter
690 f
'--pcr-private-key={priv.name}',
693 # If the public key is not explicitly specified, it is derived
694 # automatically. Let's make sure everything works as expected both when the
695 # public keys is specified explicitly and when it is derived from the
697 for extra
in ([f
'--pcrpkey={pub.name}', f
'--pcr-public-key={pub.name}'], []):
698 opts
= ukify
.parse_args(args
+ extra
)
700 ukify
.check_inputs(opts
)
706 # let's check that objdump likes the resulting file
707 dump
= subprocess
.check_output(['objdump', '-h', output
], text
=True)
709 for sect
in 'text osrel cmdline linux initrd uname pcrsig'.split():
710 assert re
.search(fr
'^\s*\d+\s+\.{sect}\s+[0-9a-f]+', dump
, re
.MULTILINE
)
712 # objcopy fails when called without an output argument (EPERM).
713 # It also fails when called with /dev/null (file truncated).
714 # It also fails when called with /dev/zero (because it reads the
715 # output file, infinitely in this case.)
716 # So let's just call it with a dummy output argument.
717 subprocess
.check_call([
719 *(f
'--dump-section=.{n}={tmp_path}/out.{n}' for n
in (
720 'pcrpkey', 'pcrsig', 'osrel', 'uname', 'cmdline')),
726 assert open(tmp_path
/ 'out.pcrpkey').read() == open(pub
.name
).read()
727 assert open(tmp_path
/ 'out.osrel').read() == 'ID=foobar\n'
728 assert open(tmp_path
/ 'out.uname').read() == '1.2.3'
729 assert open(tmp_path
/ 'out.cmdline').read() == 'ARG1 ARG2 ARG3'
730 sig
= open(tmp_path
/ 'out.pcrsig').read()
731 sig
= json
.loads(sig
)
732 assert list(sig
.keys()) == ['sha1']
733 assert len(sig
['sha1']) == 4 # four items for four phases
735 shutil
.rmtree(tmp_path
)
737 def test_pcr_signing2(kernel_initrd
, tmp_path
):
738 if kernel_initrd
is None:
739 pytest
.skip('linux+initrd not found')
740 if systemd_measure() is None:
741 pytest
.skip('systemd-measure not found')
743 ourdir
= pathlib
.Path(__file__
).parent
744 pub
= unbase64(ourdir
/ 'example.tpm2-pcr-public.pem.base64')
745 priv
= unbase64(ourdir
/ 'example.tpm2-pcr-private.pem.base64')
746 pub2
= unbase64(ourdir
/ 'example.tpm2-pcr-public2.pem.base64')
747 priv2
= unbase64(ourdir
/ 'example.tpm2-pcr-private2.pem.base64')
749 # simulate a microcode file
750 with
open(f
'{tmp_path}/microcode', 'wb') as microcode
:
751 microcode
.write(b
'1234567890')
753 output
= f
'{tmp_path}/signed.efi'
754 assert kernel_initrd
[0] == '--linux'
755 opts
= ukify
.parse_args([
758 f
'--initrd={microcode.name}',
760 f
'--output={output}',
762 '--cmdline=ARG1 ARG2 ARG3',
763 '--os-release=ID=foobar\n',
765 f
'--pcrpkey={pub2.name}',
766 f
'--pcr-public-key={pub.name}',
767 f
'--pcr-private-key={priv.name}',
768 '--phases=enter-initrd enter-initrd:leave-initrd',
769 f
'--pcr-public-key={pub2.name}',
770 f
'--pcr-private-key={priv2.name}',
771 '--phases=sysinit ready shutdown final', # yes, those phase paths are not reachable
775 ukify
.check_inputs(opts
)
781 # let's check that objdump likes the resulting file
782 dump
= subprocess
.check_output(['objdump', '-h', output
], text
=True)
784 for sect
in 'text osrel cmdline linux initrd uname pcrsig'.split():
785 assert re
.search(fr
'^\s*\d+\s+\.{sect}\s+[0-9a-f]+', dump
, re
.MULTILINE
)
787 subprocess
.check_call([
789 *(f
'--dump-section=.{n}={tmp_path}/out.{n}' for n
in (
790 'pcrpkey', 'pcrsig', 'osrel', 'uname', 'cmdline', 'initrd')),
796 assert open(tmp_path
/ 'out.pcrpkey').read() == open(pub2
.name
).read()
797 assert open(tmp_path
/ 'out.osrel').read() == 'ID=foobar\n'
798 assert open(tmp_path
/ 'out.uname').read() == '1.2.3'
799 assert open(tmp_path
/ 'out.cmdline').read() == 'ARG1 ARG2 ARG3'
800 assert open(tmp_path
/ 'out.initrd', 'rb').read(10) == b
'1234567890'
802 sig
= open(tmp_path
/ 'out.pcrsig').read()
803 sig
= json
.loads(sig
)
804 assert list(sig
.keys()) == ['sha1']
805 assert len(sig
['sha1']) == 6 # six items for six phases paths
807 shutil
.rmtree(tmp_path
)
809 def test_key_cert_generation(tmp_path
):
810 opts
= ukify
.parse_args([
812 f
"--pcr-public-key={tmp_path / 'pcr1.pub.pem'}",
813 f
"--pcr-private-key={tmp_path / 'pcr1.priv.pem'}",
814 '--phases=enter-initrd enter-initrd:leave-initrd',
815 f
"--pcr-public-key={tmp_path / 'pcr2.pub.pem'}",
816 f
"--pcr-private-key={tmp_path / 'pcr2.priv.pem'}",
817 '--phases=sysinit ready',
818 f
"--secureboot-private-key={tmp_path / 'sb.priv.pem'}",
819 f
"--secureboot-certificate={tmp_path / 'sb.cert.pem'}",
821 assert opts
.verb
== 'genkey'
822 ukify
.check_cert_and_keys_nonexistent(opts
)
824 pytest
.importorskip('cryptography')
826 ukify
.generate_keys(opts
)
828 if not shutil
.which('openssl'):
831 for key
in (tmp_path
/ 'pcr1.priv.pem',
832 tmp_path
/ 'pcr2.priv.pem',
833 tmp_path
/ 'sb.priv.pem'):
834 out
= subprocess
.check_output([
840 assert 'Private-Key' in out
841 assert '2048 bit' in out
843 for pub
in (tmp_path
/ 'pcr1.pub.pem',
844 tmp_path
/ 'pcr2.pub.pem'):
845 out
= subprocess
.check_output([
852 assert 'Public-Key' in out
853 assert '2048 bit' in out
855 out
= subprocess
.check_output([
857 '-in', tmp_path
/ 'sb.cert.pem',
861 assert 'Certificate' in out
862 assert 'Issuer: CN = SecureBoot signing key on host' in out
864 if __name__
== '__main__':
865 sys
.exit(pytest
.main(sys
.argv
))