1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
|
import hashlib
import json
import subprocess
import tempfile
import unittest
from pathlib import Path
from unittest import mock
from scripts import tpm_nvpcr, tpm_nvpcr_unmask
ROOT = Path(__file__).resolve().parents[1]
class NvPCRConfigTests(unittest.TestCase):
def test_check_recipe_accepts_no_images_or_one_image(self):
for images in ((), ("candidate.efi",)):
output = subprocess.check_output(
["just", "--dry-run", "tpm-nvpcr-check", *images],
cwd=ROOT,
stderr=subprocess.STDOUT,
text=True,
)
self.assertIn("scripts/tpm_nvpcr.py", output)
def test_hook_adds_required_initrd_services(self):
hook = ROOT / "etc/initcpio/install/nvpcr"
output = subprocess.check_output(
[
"bash",
"-c",
'map() { "$@"; }; add_systemd_unit() { printf "%s\\n" "$@"; }; source "$1"; build',
"bash",
str(hook),
],
text=True,
).splitlines()
self.assertEqual(
set(output),
{
"systemd-tpm2-setup-early.service",
"systemd-pcrnvdone.service",
"systemd-pcrextend.socket",
"systemd-pcrextend@.service",
},
)
self.assertIn(
"etc/initcpio/install/*)",
(ROOT / "run_onchange_after_deploy-etc.sh.tmpl").read_text(),
)
def test_deploy_requires_a_signed_recovery_image_and_backup(self):
script = (ROOT / "run_onchange_after_deploy-etc.sh.tmpl").read_text()
self.assertIn("backup/etc.tar", script)
self.assertIn("backup/arch-linux-lts-fallback.efi", script)
self.assertIn("sudo sbverify --cert", script)
def test_pcr7_policy_is_unchanged(self):
policy = (
ROOT / "etc/systemd/system/systemd-pcrlock-make-policy.service.d/pcr7.conf"
)
self.assertIn("--pcr=7 --location=770", policy.read_text())
for name in ("systemd-tpm2-setup.service", "systemd-tpm2-setup-early.service"):
self.assertFalse((ROOT / "etc/systemd/system" / name).exists())
def test_uki_signs_only_the_initrd_referenced_policy(self):
config = (ROOT / "etc/kernel/uki.conf").read_text()
self.assertIn("PolicyRef=initrd", config)
self.assertIn("Phases=enter-initrd", config)
self.assertNotIn("SignInitrdPCRs=", config)
def test_old_masks_are_not_in_source(self):
for name in ("cryptsetup", "hardware", "login", "verity"):
self.assertFalse((ROOT / f"etc/nvpcr/{name}.nvpcr").exists())
self.assertFalse((ROOT / "etc/mkinitcpio.conf.d/60-no-nvpcr.conf").exists())
for name in ("systemd-pcrlogin@.service", "systemd-pcrproduct.service"):
self.assertFalse((ROOT / "etc/systemd/system" / name).exists())
def sections(public_key, *, policies=None, options=None):
if policies is None:
policies = {"sha256": [{"pcrs": [11], "ref": "initrd", "sig": "signature"}]}
if options is None:
options = (
"tpm2-device=auto,tpm2-measure-pcr=15,tpm2-measure-keyslot-nvpcr=cryptsetup"
)
return {
".pcrpkey": {"sha256": hashlib.sha256(public_key).hexdigest()},
".pcrsig": {"text": json.dumps(policies)},
".cmdline": {
"text": f"rd.luks.name=uuid=root rd.luks.options=uuid={options} root=/dev/mapper/root"
},
}
class NvPCRImageTests(unittest.TestCase):
def test_command_failure_reports_stderr(self):
error = subprocess.CalledProcessError(1, ["ukify"], stderr="file not found")
with (
mock.patch.object(tpm_nvpcr.subprocess, "run", side_effect=error),
self.assertRaisesRegex(RuntimeError, "ukify exited 1: file not found"),
):
tpm_nvpcr.run("ukify", "inspect", "missing.efi")
def test_initrd_has_required_units(self):
listing = "\n".join(
f"usr/lib/systemd/system/{unit}" for unit in tpm_nvpcr.INITRD_UNITS
)
tpm_nvpcr.check_initrd_listing(listing)
def test_rejects_missing_initrd_service(self):
listing = "\n".join(
f"usr/lib/systemd/system/{unit}"
for unit in tpm_nvpcr.INITRD_UNITS
if unit != "systemd-pcrnvdone.service"
)
with self.assertRaisesRegex(ValueError, "systemd-pcrnvdone.service"):
tpm_nvpcr.check_initrd_listing(listing)
def test_valid_initrd_signature_and_root_options(self):
tpm_nvpcr.check_sections(sections(b"public key"), b"public key")
def test_rejects_wrong_public_key(self):
with self.assertRaisesRegex(ValueError, "public key"):
tpm_nvpcr.check_sections(sections(b"one"), b"two")
def test_rejects_policy_without_initrd_reference(self):
bad = {"sha256": [{"pcrs": [11], "ref": "system", "sig": "signature"}]}
with self.assertRaisesRegex(ValueError, "initrd PCR 11"):
tpm_nvpcr.check_sections(sections(b"key", policies=bad), b"key")
def test_rejects_unsigned_initrd_policy(self):
bad = {"sha256": [{"pcrs": [11], "ref": "initrd"}]}
with self.assertRaisesRegex(ValueError, "initrd PCR 11"):
tpm_nvpcr.check_sections(sections(b"key", policies=bad), b"key")
def test_rejects_missing_tpm_selection(self):
with self.assertRaisesRegex(ValueError, "option is missing"):
tpm_nvpcr.check_sections(
sections(
b"key",
options="tpm2-measure-pcr=15,tpm2-measure-keyslot-nvpcr=cryptsetup",
),
b"key",
)
def test_rejects_wrong_root_uuid(self):
image = sections(b"key")
image[".cmdline"]["text"] = image[".cmdline"]["text"].replace(
"rd.luks.options=uuid=", "rd.luks.options=other="
)
with self.assertRaisesRegex(ValueError, "wrong LUKS UUID"):
tpm_nvpcr.check_sections(image, b"key")
class NvPCRUnmaskTests(unittest.TestCase):
def test_refuses_any_modified_file_before_deleting_masks(self):
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
first = root / "first.nvpcr"
changed = root / "second.nvpcr"
first.touch()
changed.write_text("owned by user")
with self.assertRaisesRegex(ValueError, "not empty"):
tpm_nvpcr_unmask.validate([first, changed])
self.assertTrue(first.exists())
def test_accepts_only_expected_dropin_contents(self):
with tempfile.TemporaryDirectory() as directory:
dropin = Path(directory) / "dropin.conf"
dropin.write_text("changed")
with (
mock.patch.object(tpm_nvpcr_unmask, "DROPIN", dropin),
self.assertRaisesRegex(ValueError, "was changed"),
):
tpm_nvpcr_unmask.validate([dropin])
if __name__ == "__main__":
unittest.main()
|